Question

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.
Was it helpful?

Solution 2

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 (+))

OTHER TIPS

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)".

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top