The following code doesn't compile...
namespace {
template<typename T, template<typename> class D>
struct Base {
Base(const T& _t) : t(_t) { }
T t;
};
template<typename T>
struct Derived : Base<T, Derived> {
Derived(const T& _t) : Base<T, Derived>(_t) { }
};
}
int main(int argc, char* argv[]) {
Derived<int> d(1);
return 0;
}
There is a compilation error on the line - Derived(const T& _t) : Base<T, Derived>(_t) { }
Error C3200 '`anonymous-namespace'::Derived': invalid template argument for template parameter 'D', expected a class template
This works if I provide any other class that has template argument instead of Derived itself
template<typename T>
struct Other {
};
template<typename T>
struct Derived : Base<T, Other> {
Derived(const T& _t) : Base<T, Other>(_t) { }
};
Tl;dr: the most portable and least extensive way to get around that problem seems to be using the qualified name ::Derived
in your example:
template<typename T>
struct Derived : Base<T, Derived>
{
Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
The Problem is compiler non-conformance to C++11.
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-typespecifier of a friend class template declaration, it refers to the class template itself.
Thus your code should compile but unfortunately, all compilers I tested (clang 3.7, Visual Studio 2015 and g++5.3) refuse to do it.
Afaik, you should be able to denote the template in various ways, inside the Derived
definition:
Derived
::Derived
Derived<T>::template Derived
::template Derived
The compilation status of those compilers regarding those four options is as follows (using the anonymus namespace; where +
= successful compilation):
+------------------------------+----------+---------+-----------+
| Method | MSVS2015 | g++ 5.3 | clang 3.7 |
+------------------------------+----------+---------+-----------+
| Derived | - | - | - |
| ::Derived | + | + | + |
| Derived<T>::template Derived | - | - | + |
| ::template Derived | + | - | + |
+------------------------------+----------+---------+-----------+
When giving the namespace the name X
, the picture changes a little (namely g++
now accepts X::template Derived
whereas it rejected ::template Derived
):
+---------------------------------+----------+---------+-----------+
| Method | MSVS2015 | g++ 5.3 | clang 3.7 |
+---------------------------------+----------+---------+-----------+
| Derived | - | - | - |
| X::Derived | + | + | + |
| X::Derived<T>::template Derived | - | - | + |
| X::template Derived | + | + | + |
+---------------------------------+----------+---------+-----------+
In a class template, the injected-class-name (Derived
in your example) can be both a type name and a template name. The standard specifies that it should be considered to name the template when used as an argument to a template template parameter (so your code should work), but unfortunately some compilers haven't yet implemented this.
One workaround is to use a qualified name, so that you don't use the injected-class-name but directly name the template:
template<typename T>
struct Derived : Base<T, Derived> {
Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With