Reputation: 4254
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
Upvotes: 5
Views: 1371
Reputation: 13526
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 (+))
Upvotes: 2
Reputation: 507005
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)".
Upvotes: 5