Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CRTP with Template Template Arguments

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) { }
};
like image 241
Sriram Pyngas Avatar asked Jan 12 '16 09:01

Sriram Pyngas


2 Answers

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) { }
};

Why?

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:

  • Using the injected name directly Derived
  • Qualified name of the class template ::Derived
  • Using the injected class name, designating it as a template name Derived<T>::template Derived
  • Using a qualified name, again designating it as a template name ::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             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+
like image 154
Pixelchemist Avatar answered Nov 14 '22 20:11

Pixelchemist


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) { }
};
like image 15
T.C. Avatar answered Nov 14 '22 20:11

T.C.