19

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) { }
};
Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 2
    The reason is that `D` of `Base` expects a template. Inside `Derived` the name `Derived` is the injected instance of `Derived` (namely `Derived` in your case) which is not a template but a concrete type. – Pixelchemist Jan 12 '16 at 09:20
  • Possible duplicate of [How do I use the class-name inside the class itself as a template argument?](http://stackoverflow.com/questions/17287778/how-do-i-use-the-class-name-inside-the-class-itself-as-a-template-argument) – Pixelchemist Sep 09 '16 at 01:00

2 Answers2

18

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             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
15

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) { }
};
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • "some compilers" which? – underscore_d Jan 12 '16 at 09:31
  • @PiotrSkotnicki You think it should always be a *template-name* and can't be used as a *type-name*? – T.C. Jan 12 '16 at 09:33
  • 1
    @PiotrSkotnicki: Correct. `The point of declaration for an injected-class-name (Clause 9) is immediately following the opening brace of the class definition.` – Pixelchemist Jan 12 '16 at 09:35
  • @PiotrSkotnicki Oh, right, in the base-specifier. You are right - everyone thinks that one is a *template-name*. Fixed. – T.C. Jan 12 '16 at 09:40
  • Denoting the class template using the injected name through `Derived::template Derived` compiles using clang 3.7 but is rejected by MSVC 2015 as well as g++5.3. – Pixelchemist Jan 12 '16 at 10:27
  • @Pixelchemist MSVC is weird; it rejects in mem-initializers but not elsewhere. Going to remove it anyway, no point having a workaround that only partially works. – T.C. Jan 12 '16 at 10:36