Подтвердить что ты не робот

CRTP с аргументами шаблона шаблона

Следующий код не компилируется...

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

В строке есть ошибка компиляции - Derived(const T& _t) : Base<T, Derived>(_t) { }

Ошибка C3200 '`anonymous-namespace':: Derived ': недопустимый шаблон аргумент для параметра шаблона 'D', ожидаемый шаблон класса

Это работает, если я предоставляю любой другой класс с аргументом шаблона вместо самого Derived

template<typename T>
struct Other {

};
template<typename T>
struct Derived : Base<T, Other> {
    Derived(const T& _t) : Base<T, Other>(_t) { }
};
4b9b3361

Ответ 1

Tl; dr: самый портативный и наименее обширный способ обойти эту проблему, похоже, использует в вашем примере квалифицированное имя ::Derived:

template<typename T>
struct Derived : Base<T, Derived>
{
  Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};

Почему?

Проблема заключается в несоответствии компилятора С++ 11.

Подобно обычным (не шаблонным) классам, шаблоны классов имеют имя с введенным классом (раздел 9). Введенное имя класса можно использовать в качестве имени шаблона или имени типа. Когда он используется с шаблоном-аргументом-списком, в качестве аргумента-шаблона для шаблона-шаблона шаблона или в качестве конечного идентификатора в специфицированном указателе типа объявление класса класса друга, относится к самому шаблону класса.

Таким образом, ваш код должен компилироваться, но, к сожалению, все компиляторы, которые я тестировал (clang 3.7, Visual Studio 2015 и g++ 5.3), отказываются это делать.

Afaik, вы должны иметь возможность обозначать шаблон различными способами внутри определения Derived:

  • Использование введенного имени напрямую Derived
  • Квалифицированное имя шаблона класса ::Derived
  • Используя имя введенного класса, обозначив его как имя шаблона Derived<T>::template Derived
  • Используя квалифицированное имя, снова обозначив его как имя шаблона ::template Derived

Состояние компиляторов этих четырех параметров выглядит следующим образом (с использованием пространства имен anonymus, где += успешная компиляция):

+------------------------------+----------+---------+-----------+
|           Method             | MSVS2015 | g++ 5.3 | clang 3.7 |
+------------------------------+----------+---------+-----------+
| Derived                      |    -     |    -    |     -     |
| ::Derived                    |    +     |    +    |     +     |
| Derived<T>::template Derived |    -     |    -    |     +     |
| ::template Derived           |    +     |    -    |     +     |
+------------------------------+----------+---------+-----------+

При присвоении пространству имен имя X изображение немного меняется (а именно g++ теперь принимает X::template Derived, тогда как оно отклонено ::template Derived):

+---------------------------------+----------+---------+-----------+
|            Method               | MSVS2015 | g++ 5.3 | clang 3.7 |
+---------------------------------+----------+---------+-----------+
| Derived                         |    -     |    -    |     -     |
| X::Derived                      |    +     |    +    |     +     |
| X::Derived<T>::template Derived |    -     |    -    |     +     |
| X::template Derived             |    +     |    +    |     +     |
+---------------------------------+----------+---------+-----------+

Ответ 2

В шаблоне класса имя введенного класса (Derived в вашем примере) может быть как именем типа, так и именем шаблона. В стандарте указано, что его следует рассматривать как имя шаблона при использовании в качестве аргумента для параметра шаблона шаблона (поэтому ваш код должен работать), но, к сожалению, некоторые компиляторы еще не реализовали это.

Одним из способов решения проблемы является использование квалифицированного имени, чтобы вы не использовали имя введенного класса, но напрямую указывали шаблон:

template<typename T>
struct Derived : Base<T, Derived> {
    Derived(const T& _t) : Base<T, ::Derived>(_t) { }
};