template deduction: const reference and const pointer - c++

template <typename T>
void f(T t)
{}
int x = 1;
const int & rx = x;
const int * px = &x;
f(rx); // t is int
f(px); // t is const int *, instead of int *, WHY???
I'm confused now. According to Effective Modern c++,
It’s important to recognize that const is ignored only for by-value
parameters. As we’ve seen, for parameters that are references-to- or
pointers-to-const, the constness of expr is preserved during type
deduction.
I think it means
template <typename T>
void f(T * t)
{}
f(px); // t is const int *
template <typename T>
void f(T & t)
{}
f(cx); // t is const int &
template <typename T>
void f(T t)
{}
f(value); // const or volatile of value will be ignored when the type of the parameter t is deduced
So I think when f(px) above, t should be int *, but in fact it is const int *.
Why the const of reference is ignored but the const of pointer isn't?
Or, why isn't rx const int &?

So I think when f(px) above, px should be int *, but in fact it is const int *.
The point is the type of parameter, behaviors change for passed-by-value and passed-by-reference/pointer.
When passed-by-value, the constness of the argument itself is ignored. For a pointer parameter, the constness of pointer is ignored (const pointer -> pointer), but the constness of pointee is still preserved (pointer to const -> pointer to const).
It does make sense because when pass a pointer, the pointer itself is copied, but the pointee is the same, they're both pointing to the same thing, so constness of pointee is preserved; from the point of callers, they won't want the object to be modified, they might use it later. While pass a reference (reference doesn't matter in fact here), you'll get a brand new value copied, which has nothing to do with the original one, then constness is ignored.
As the following explanations in the book, when const pointer to const (a const char* const) passed,
template<typename T>
void f(T param); // param is still passed by value
const char* const ptr = // ptr is const pointer to const object
"Fun with pointers";
f(ptr); // pass arg of type const char * const
The type deduced for param will be const char*.

Only top-level const/volatile qualifiers are ignored. All others are inherent qualities of your type. In other words, you're copying a pointer - it means the function operates on a copy and any modifications to it (like assigning another variable's address) do not modify the original pointer. But if you pass a pointer to const int, having the function modify the integer is very much counter-intuitive.
template <typename T>
void f(T t)
{
t = &another_variable; // ok
}
f(px);
and
void f(T t)
{
*t = 42; // not ok!
}
f(px); // secretly modifying `x` through a pointer to const...
Reference: this answer for pointer to const vs. const pointer differences

Related

function template parameter loses const when template argument is explicity passed?

template <typename T>
void test(const T& x) {}
int a {};
int& ref = a;
const int& c_ref = a;
test(c_ref) // T = int, x = const int&
test<int&>(ref); // T = int& , x = int&
Why does the function template parameter x loses the const qualifier?
In the explicit (non-deduced) instantiation
test<int&>(ref);
this is the (theoretical) signature you get
void test<int&>(const (int&)& x)
which shows that the const-qualification applies to the whole (int&), and not only the int. const applies to what is left, and if there's nothing, it applies to what is right: int&, but as a whole - there, it applies to &, again because const applies to what's on its left. But there are no const references (they aren't changeable at all, i.e., they can't rebind), the const is dropped, and reference collapsing rules contract the two & into one.

Why is type deduction on const char[] different to const char *?

In the first call, when I pass a char const [] into a template function with a parameter of T const a, T is deduced as char const * which is reasonable because const refers to the decaying pointer.
However, when the parameter type is changed to T const & a, T is deduced as char[7]. From the point of view above, why doesn't the const qualify the whole array type?
template <typename T>
void show1(T const a) {
// input is const char *
// T is char const *
// a is char const * const
}
template <typename T>
void show2(T const & a) {
// input is char const [7]
// T is char[7]
// a is char const (&)[7]
}
int main() {
const char s[] = "asdasd";
show1(s);
show2(s);
}
why doesn't the const qualify the whole array type
Because for array type,
(emphasis mine)
Applying cv-qualifiers to an array type (through typedef or template type manipulation) applies the qualifiers to the element type, but any array type whose elements are of cv-qualified type is considered to have the same cv-qualification.
// a and b have the same const-qualified type "array of 5 const char"
typedef const char CC;
CC a[5] = {};
typedef char CA[5];
const CA b = {};
That means when T is char[7] T const leads to the type char const[7], then T const& (i.e. a's type) is char const (&)[7].
On the other hand, when you pass the array s with type const char[7], the array is considered as const-qualified too. So given the parameter type T const&, T is deduced as char[7] (but not char const[7]).
This is because arrays are non-copyable and non-assignable in C++.
So in calling show1, the const char[] type decays to a const char*. The language permits one implicit conversion per parameter at a function call site.
With show2, you are passing by reference - no copy or assignment is required, so pointer decay does not occur.
template <typename T>
void show(ParameterType a) {
}
show(expression);
Compiler uses expression to deduce T and ParameterType. T and ParameterType are different if ParameterType contains qualifiers like const.
If ParameterType is neither pointer nor reference (case of your show1, T const), type of T is type of expression without const, volatile and reference. So T is type of const char *. ParameterType (type of a) is const char * const.
If ParameterType (T const & in your show2) is pointer or reference (but not reference like T&&). First ignore reference, which gives results T const (same as const T). Second match type of expression (const char []) to const T, so T is char [].
For more information, Item 1 of Effective Modern C++ by Scott Meyers is exactly what you want. The rule is more complicated then which I described here but very important.

How to filter const types and non const types using meta programing?

I have this code
#include <iostream>
size_t F()
{
return 0;
}
template <class Type, class... NextTypes>
size_t F(const Type& type, const NextTypes&... nextTypes)
{
if (!std::is_const<Type>::value)
return sizeof(type) + F(nextTypes...);
else
return F(nextTypes...);
}
int main()
{
int a = 0;
const int b = 0;
const size_t size = F(a,b);
std::cout << "size = " << size << std::endl;
return 0;
}
I'm trying to know in compilation time the total size of constant parameters and non const parameters. The current out put is 8, for some reason the compiler thinks b is not constant, I used typeid and decltype to print the types of a and b and indeed the output shows b is an int and not const int as I expected. What am I missing? Is it possible to separate a variadic set of arguments to const arguments and non const?
Consider this function template:
template<typename T>
void deduce(const T&);
If you let the compiler deduce a type for T from an argument expression, the deduced type will never be const: It will try to make the const T of the function parameter identical to the type of the argument expression used to call the function. For example:
struct cls {};
const cls c;
deduce(c) // deduces T == cls
By deducing T == cls, the compiler succeeds in making const T identical to the argument type const cls. There is no reason for the compiler to produce two different functions for const- and non-const argument types; the parameter type of the function template instantiation will be const-qualified in any case: you requested it by saying const T& instead of, say, T&.
You can deduce the const-ness of an argument by not cv-qualifying the function parameter:
template<typename T>
void deduce(T&);
However, this will fail to bind to non-const temporaries (rvalues). To support them as well, you can use universal references:
template<typename T>
void deduce(T&&);
This will deduce an lvalue-reference type for T if the argument is an lvalue, and no reference if the argument is an rvalue. The const-ness will be deduced correctly.
For example, if the argument has the type const A and is an lvalue, T will be deduced to const A&. The function parameter then is const A& &&, which is collapsed to const A& (an lvalue-reference). If the argument is an rvalue, T will be deduced to const A, and the function parameter becomes const A&& (an rvalue-reference).
Note that since T can be a reference in this case, you need to remove that before checking for const-ness: std::is_const< typename std::remove_reference<T>::type >::value.

C++ passing a const pointer by const reference

Hmm a strange one in VC2012 I can't seem to work out the syntax for passing a const pointer by const reference into a function of a templated class whose template argument is a non const pointer ie:
template<typename T>
struct Foo
{
void Add( const T& Bar ) { printf(Bar); }
};
void main()
{
Foo<char*> foo;
const char* name = "FooBar";
foo.Add(name); // Causes error
}
So I've simplified my problem here but basically I want the argument to 'Add' to have a const T ie const char*. I've tried:
void Add( const (const T)& Bar );
typedef const T ConstT;
void Add( const (ConstT)& Bar );
void Add( const typename std::add_const<T>::type& Bar );
None of which work. The exact error I'm getting is:
error C2664: 'Foo<T>::Add' : cannot convert parameter 1 from 'const char *' to 'char *const &'
with
[
T=char *
]
Conversion loses qualifiers
which I can see is correct but how do I solve it without const casting 'name' to be non const.
There is a strong difference between a pointer to a constant object (T const*, or const T*) and a constant pointer to a non-constant object (T * const). In your case the signature of the member Add is:
void Foo<char *>::Add(char * const& ); // reference to a constant pointer to a
// non-constant char
I usually recommend that people drop the use of const on the left hand side exactly for this reason, as beginners usually confuse typedefs (or deduced types) with type substitution and when they read:
const T& [T == char*]
They misinterpret
const char*&
If the const is placed in the right place:
T const &
Things are simpler for beginners, as plain mental substitution works:
char * const &
A different problem than what you are asking, but maybe what you think you want, is:
Given a type T have a function that takes a U that is const T if T is not a pointer type, or X const * if T is a pointer to X
template <typename T>
struct add_const_here_or_there {
typedef T const type;
};
template <typename T>
struct add_const_here_or_there<T*> {
typedef T const * type;
};
Then you can use this in your signature:
template <typename T>
void Foo<T>::Add( const typename add_const_here_or_there<T>::type & arg ) {
...
Note that I am adding two const in the signature, so in your case char* will map to char const * const &, as it seems that you want to pass a const& to something and you also want the pointed type to be const.
You might have wondered as of the name for the metafunction: *add_const_here_or_there*, it is like that for a reason: there is no simple way of describing what you are trying to do, which is usually a code smell. But here you have your solution.
It looks like your issue here as that as soon as you have a pointer type mapped to a template type, you can no longer add const-ness to the pointed-to type, only to the pointer itself. What it looks like you're trying to do is automatically add constness to the parameter of your function (so if T is char* the function should accept const char* const& rather than char* const& as you've written). The only way to do that is with another template to add constness to the pointee for pointer types, as follows. I took the liberty of including missing headers and correcting the signature of main:
#include <cstdio>
template<typename T>
struct add_const_to_pointee
{
typedef T type;
};
template <typename T>
struct add_const_to_pointee<T*>
{
typedef const T* type;
};
template<typename T>
struct Foo
{
void Add( typename add_const_to_pointee<T>::type const & Bar ) { printf(Bar); }
};
int main()
{
Foo<char*> foo;
const char* name = "FooBar";
foo.Add(name); // Causes error
}
As mentioned in another another however, this issue just goes away if you use std::string instead of C-style strings.
You need to change the template argument to your Foo object to Foo<const char*>. Because if T=char*, then const T=char*const, not const char*. Trying to coerce it to work is not a good idea and would probably result in undefined behavior.
Use:
Foo<const char*> foo;
const char* name = "FooBar";
foo.Add(name);
And write int main() instead of void main()
If passing const char* instead of char* to Foo is not an option you can finesse the correct type with std::remove_pointer. This will remove the pointer modifier and allow you to provide a more explicit type.
#include <type_traits>
template<typename T>
struct Foo
{
void Add(typename std::remove_pointer<T>::type const*& Bar ) { printf(Bar); }
};
To prevent the pointer value from being modified you can declare the reference as const as well.
void Add(typename std::remove_pointer<T>::type const* const& Bar )
{ Bar = "name"; } // <- fails
If you need to reduce the type from say a pointer to pointer you can use std::decay along with std::remove_pointer
void Add(typename std::remove_pointer<typename std::decay<T>::type>::type const*& Bar)
{
printf(Bar);
}
This really depends on what your requirements for T are. I suggest assuming only the base type (e.g. char) is passed as T and building reference and pointer types from that.

Template argument deduction and const qualification

Could anyone explain why the code does not compile.
template<class T, class DER>
struct Base {
T a;
Base(const T argB) : a(argB){}
};
template<class T>
struct Derived : Base<T, Derived<T> > {
Derived(const T argD) : Base<T, Derived<T> >(argD){}
};
int main() {
int val = 10;
const int *p = &val;
/* this was in the original question
Derived<int*> d(p); // breaks, but compiles with Derived<const int*> d(p);
*/
Derived d(p); // fails, but Derived<const int*> d(p); compiles
}
The error message is that about no conversion from int* to const int*. As I see it T can be substitues by int* and in that case the constructor to Derived receives its argument as a const int* and invokes the base with const int*. Why then is the constant qulaification getting lost.
I clearly do not understand how template argument deduction works. I have not been able to find any lucid but rigorous and exhaustive description of how it works when const, * and & are in play. That is, what will a get type deduced to in these various cases.
Foo(T& a)
Foo(T a)
Foo(T* a)
Foo(const T a)
Foo(const T*a)
Foo(const t&a)
when a is
an object,
a pointer and
an array.
Because the constructor of Derived is Derived(const T argD), so in your case it is Derived(int * const). This does not accept an const int*.
A "const (pointer to int)" is not a "(pointer to const int)".
There is no template deduction in your example. Derived<int*> d(p); specifically sets the template parameter to T to int* (pointer to int). Your derived constructor takes a const T parameter which is a const pointer to int in this case. I think your confusion is because const int* p; does not declare a const pointer to int, but instead declares a pointer to const int which is not, and cannot be converted to, a const pointer to int (the former lets you modify the pointed to value while the latter does not).
Remember that C and C++ declarations are generally read from the variable name outwards, so for const int* p you start at p, go left and see * and then go farther left and see const int, so p is a pointer to a const int. Here is a good guide on deciphering C declarations and cdecl is also a very useful tool.
The problem with your example is p is a pointer to const int, but the constructor of Derived<int*> takes a const pointer to int since T is int*. This may seem confusing, but you can think of const as having a higher precedence than * in type declarations. So in const int * the const applies to int and then * applies to the whole thing making p a pointer to const int whereas for const T, the const applies to T, which is actually int* so const T argD makes argD a const pointer to int.
Using this same idea all your Foo examples can be easily deciphered.
Foo(T& a) // a is a reference to a value of type T
Foo(T a) // a is a value of type T
Foo(T* a) // a is a pointer to a value of type T
Foo(const T a) // a is a constant value of type T
Foo(const T* a) // a is a pointer to a constant value of type T
Foo(const T& a) // a is a reference to a constant value of type T
In general only Foo(T a) and Foo(const T a) cannot be overloaded because it doesn't matter to the caller whether the argument is copied into a constant variable or not.
More specifically, if T is char * (pointer to char)
Foo(char *&a) // a is a reference to a pointer to a char
Foo(char *a) // a is a pointer to a char (*)
Foo(char **a) // a is a pointer to a pointer to a char
Foo(char *const a) // a is a constant pointer to a char (cannot overload with (*))
Foo(char *const *a) // a is a pointer to a constant pointer to a char
Foo(char *const &a) // a is a reference to a constant pointer to a char
If T is const char* (pointer to a const char) things are much the same
Foo(const char *&a) // a is a reference to a pointer to a const char
Foo(const char *a) // a is a pointer to a const char (*)
Foo(const char **a) // a is a pointer to a pointer to a const char
Foo(const char *const a) // a is a constant pointer to a const char (cannot overload with (*))
Foo(const char *const *a) // a is a pointer to a constant pointer to a const char
Foo(char *const &a) // a is a reference to a constant pointer to a const char
If T is char* const (const pointer to a char) then all the const T overloads are redundant because const T is equivalent to T when T is already const.
Foo(char *const &a) // a is a reference to a const pointer to a char (+)
Foo(char *const a) // a is a const pointer to a char (*)
Foo(char *const *a) // a is a pointer to a const pointer to a char (^)
Foo(char *const a) // a is a const pointer to a char (same as (*))
Foo(char *const *a) // a is a pointer to a const pointer to a char (same as (^))
Foo(char *const &a) // a is a reference to a const pointer to a char (same as (+))