Template base constructor call in member initialization list error
First:
[C++11: 12.6.2/3]:
A mem-initializer-list can initialize a base class using any class-or-decltype that denotes that base class type.[ Example:
struct A { A(); };
typedef A global_A;
struct B { };
struct C: public A, public B { C(); };
C::C(): global_A() { } // mem-initializer for base A
—end example ]
And Base
should be a valid injected-class-name for the base here (that is, you can use it in place of Base<T>
):
[C++11: 14.6.1/1]:
Like normal (non-template) classes, class templates have an injected-class-name (Clause 9). The injected-class-name can be used as a template-name or a type-name. When it is used with a template-argument-list, as a template-argument for a template template-parameter, or as the final identifier in the elaborated-type-specifier
of a friend class template declaration, it refers to the class template itself. Otherwise, it is equivalent to the template-name followed by the template-parameters of the class template enclosed in<>
.
[C++11: 14.6.1/3]:
The injected-class-name of a class template or class template specialization can be used either as a template-name or a type-name wherever it is in scope. [ Example:template <class T> struct Base {
Base* p;
};
template <class T> struct Derived: public Base<T> {
typename Derived::Base* p; // meaning Derived::Base<T>
};
template<class T, template<class> class U = T::template Base> struct Third { };
Third<Base<int> > t; // OK: default argument uses injected-class-name as a template
—end example ]
I haven't found anything to indicate that this doesn't apply in the ctor-initializer, so I'd say that this is a compiler bug.
My stripped-down testcase fails in GCC 4.1.2 and GCC 4.3.4 but succeeds in GCC 4.5.1 (C++11 mode). It seems to be resolved by GCC bug 189; in the GCC 4.5 release notes:
G++ now implements DR 176. Previously G++ did not support using the
injected-class-name of a template base class as a type name, and
lookup of the name found the declaration of the template in the
enclosing scope. Now lookup of the name finds the injected-class-name,
which can be used either as a type or as a template, depending on
whether or not the name is followed by a template argument list. As a
result of this change, some code that was previously accepted may be
ill-formed because
- The injected-class-name is not accessible because it's from a private base, or
- The injected-class-name cannot be used as an argument for a template template parameter.
In either of these cases, the code can be fixed by adding a
nested-name-specifier to explicitly name the template. The first can
be worked around with -fno-access-control; the second is only rejected
with -pedantic.
My stripped-down testcase with Qt abstracted out:
template <typename T>
struct Base { };
struct Derived : Base<Derived> { // I love the smell of CRTP in the morning
Derived();
};
Derived::Derived() : Base() {};
How to initialize members of template-base class from derived constructor initializer list?
You need to initialize the base class in the member initialization list of the derived class. Since your base doesn't have a constructor you can use curly brace initialization (uniform initialization) like
template <typename T>
struct base {
T a;
};
template <typename T>
struct derived : base<T> {
derived(T v) : base<T>{v} {}
};
Compiler Error When Calling Base Constructor when both Base and Derived are Templated With Derived Type Parameter
The injected class name Base
is a member of the class Base
, and since the base class is dependent, its scope is not searched during unqualified name lookup. As such, using the name Base
will only find the class template, not the injected class name of Base<T>
. That is why you must write Base<T>
.
Derived::Base
works because it causes name lookup to be postponed until Derived
is instantiated.
Initializing template base-class member types in derived-class initializer lists
The Foo_T type will not be looked up in the base class when used in the derived (Bar) constructor.
Bar (const foo_arg_t bar_arg, const a_arg_t a_arg)
: Foo<T>(bar_arg) // base-class initializer
{
Foo_T = TypeA(a_arg); TypeA, etc. // Won't compile, per the standard
}
This is per the C++ standard, which says unqualified names are generally non-dependent, and should be looked up when the template is fully defined.
Since a template base class definition is not known at that time (there could be fully specialised instances of the template being pulled in later in the compilation unit), unqualified names are never resolved to names in dependent base classes.
If you need a name from a base class when templates are involved, you have to either fully qualify them, or make them implicitly dependent in your derived class.
Foo< T >::Foo_T = TypeA(a_arg); // fully qualified will compile
or, make it dependent
this->Foo_T = TypeA(a_arg);
Since the this
makes it template dependent, resolving the type is postponed till "phase 2" of template instantiation (and then, the base class is also fully known)
Note that if you wanted to use a function from the base class, you could have also added a using declaration..
(inside Bar())
some_foo_func(); // wouldn't work either
using Foo<T>::some_foo_func;
some_foo_func(); // would work however
Why can't Initialize the data member of base class in the constructor initializer list of derived class?
By the time the constructor of the derived class is invoked, the base class must already be constructed. So it's already too late. To see why it must be this way, consider:
class Base
{
public:
int i;
Base(int q) : i(q) { ; }
};
class Middle : public Base
{
public:
Middle() : Base(2) { printf("i=%d\n", i); }
};
class Derived : public Middle
{
public:
Derived() : i(3) { ; }
}
Now, think about it. The Middle
constructor has to run before the Derived
constructor. And the Middle
constructor ensures that i
is 2. So how can the Derived
constructor later re-construct it with a different value?
Related Topics
Trying to Pass String Literals as Template Arguments
Why Is the New Operator Allowed to Return *Void to Every Pointer-Type
How to Specify Vc11 Lambda Calling Convention
Disable Sleep Mode in Windows Mobile 6
C++: Std::Move with Rvalue Reference Is Not Moving Contents
How to Overload Array Index Operator for Wrapper Class of 2D Array
System':A Namespace with This Name Does Not Exist
What Is the Meaning of Double Curly Braces Initializing a C-Struct
Should You Overload Swap in the Std Namespace
Should "Delete This" Be Called from Within a Member Method
C++: Function Pointer to Functions with Variable Number of Arguments
What Does C4250 Vc++ Warning Mean
Why Class Size Depend Only on Data Members and Not on Member Functions
C++ 128/256-Bit Fixed Size Integer Types
Insert into an Stl Queue Using Std::Copy
C++: Calling Member Function via Pointer
How to Convert a String Literal to Unsigned Char Array in Visual C++