Нет типа с именем 'тип' в производном классе CTRP

Я экспериментировал с шаблоном Curiously Recurring Template для общего функтора с одним аргументом и имею две реализации: одна с использованием параметра шаблона шаблона, который работает, и вторая, где я пытаюсь получить доступ к производному Functor:: type в классе интерфейса. В последнем примере компилятор (gcc 5.4.0) сообщает

ошибка: нет типа с именем 'type' в 'struct Cube'

template<class T, template<class> class Functor>
class FunctorInterface_1 {
private:
  const Functor<T> &f_cref;
public:
  FunctorInterface_1() : f_cref(static_cast<const Functor<T>&>(*this)) {}
  T operator() ( T val ) const { return f_cref(val); }
}; // FunctorInterface_1 (works)

template<class Functor>
class FunctorInterface_2 {
private:
  const Functor &f_cref;
public:
  using Ftype = typename Functor::type;
  FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {}
  Ftype operator() ( Ftype val ) const { return f_cref(val); }
}; // FunctorInterface_2 (no type in Functor!)

Затем я пытаюсь скомпилировать с T=double в main() следующих двух классов:

template<class T> 
struct Square : public FunctorInterface_1<T,Square> {
  T operator()( T val ) const { return val*val; }
}; // Square


template<class T>
struct Cube : public FunctorInterface_2<Cube<T>> {
  using type = T; 
  T operator() ( T val ) const { return val*val*val; }
}; // Cube

Можно ли изменить пример FunctorInterface_2/Cube для работы или необходимо, чтобы интерфейсный класс был настроен на T, как в первом примере? Спасибо!

РЕДАКТИРОВАТЬ: Используя gcc -std = C++14, я могу получить второй пример для компиляции и запуска с использованием автоматического возврата и типов аргументов в FunctorInterface_1::operator(), однако, как я понимаю, автоматические типы аргументов не являются частью стандарт C++14.

РЕДАКТИРОВАТЬ 2: Ну, я чувствую себя немного толстым. Я только что понял, что могу создать шаблон FunctorInterface_1::operator() для нового параметра, однако, для приложения, которое я имею в виду, я действительно хотел бы, чтобы мой базовый класс имел возможность доступа к типам, определенным в производном классе.

3 ответа

Решение

Когда линия

using Ftype = typename Functor::type;

обрабатывается в базовом классе, определение Functor не доступен. Следовательно, вы не можете использовать Functor::type,

Один из способов обойти это ограничение - определить класс черт.

// Declare a traits class.
template <typename T> struct FunctorTraits;

template<class Functor>
class FunctorInterface_2 {
   private:
      const Functor &f_cref;
   public:

      // Use the traits class to define Ftype
      using Ftype = typename FunctorTraits<Functor>::type;

      FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {}
      Ftype operator() ( Ftype val ) const { return f_cref(val); }
}; // FunctorInterface_2 (no type in Functor!)

// Forward declare Cube to specialize FunctorTraits
template<class T> struct Cube;

// Specialize FunctorTraits for Cube
template <typename T> struct FunctorTraits<Cube<T>>
{
   using type = T; 
};

template<class T>
struct Cube : public FunctorInterface_2<Cube<T>> {
   using type = T; 
   T operator() ( T val ) const { return val*val*val; }
}; // Cube

Рабочий код: https://ideone.com/C1L4YW

Ваш код может быть упрощен до

template<typename TDerived> class
Base
{
    using Ftype = typename TDerived::type;
};

template<typename T> class
Derived: public Base<Derived<T>>
{
    using type = T;
};

Derived<int> wat;

Это не работает, потому что в точке Base конкретизации Derived класс не завершен, и компилятор не знает Derived::type существование еще.

Вы должны понимать, что когда вы создаете Cube<T>FunctionInterface_2<Cube<T>> получает экземпляр первым. Это означает, что Cube<T> это неполный тип, пока это происходит.
Поэтому, когда компилятор попадает в строку, используя Ftype = typename Functor::type;Functor является неполным, и вы не можете получить доступ ни к одному из его вложенных типов.

В вашем случае вы можете изменить FunctionInterface_2 чтобы:

template<class Functor>
class FunctorInterface_2 {
private:
    const Functor &f_cref;
public:
    FunctorInterface_2() : f_cref(static_cast<const Functor&>(*this)) {}
    template <class TT>
    auto operator() ( TT && val ) -> decltype(f_cref(val)) const { return f_cref(val); }
};

Так что теперь доступ к информации о Functor задерживается, пока вы не позвоните operator() от FunctionInterface_2 в какой момент FunctionInterface_2 а также Cube полностью созданы.

Примечание: на этот вопрос уже ответил @ r-sahu, но я бы хотел остановиться на этом и конкретно остановиться на выводе clang.

Проблема может быть продемонстрирована на гораздо меньшем примере кода: (@vtt предложил нечто подобное)

template <typename _CRTP>
struct A {
    using _C = typename _CRTP::C;
};

struct B : public A<B> {
    using C = int;
};

Компиляция с помощью clang приведет к полностью вводящему в заблуждение сообщению об ошибке: ( godbolt)

<source>:3:32: error: no type named 'C' in 'B'
    using _C = typename _CRTP::C;
               ~~~~~~~~~~~~~~~~^
<source>:6:19: note: in instantiation of template class 'A<B>' requested here
struct B : public A<B> {
                  ^
1 error generated.

Сообщение об ошибке GCC немного более полезно: ( Godbolt)

<source>: In instantiation of 'struct A<B>':
<source>:6:19:   required from here
<source>:3:33: error: invalid use of incomplete type 'struct B'
    3 |     using _C = typename _CRTP::C;
      |                                 ^
<source>:6:8: note: forward declaration of 'struct B'
    6 | struct B : public A<B> {
      |        ^

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

// this declaration must appear before the definition of A
template <typename _A>
struct a_traits;

template <typename _CRTP>
struct A {
    // `a_traits<_CRTP>::type` is an incomplete type at this point,
    // but that doesn't matter since `A` is also incomplete
    using _C = typename a_traits<_CRTP>::type;
};

// this specialization must appear before the definition of B
template <>
struct a_traits<struct B> { // adding the type specifier `struct` will declare B
    using type = int;
};

// specifying the template parameter will complete the type `A<B>`, which works since
// `a_traits<B>` is already complete at this point
struct B : public A<B> {
    using C = int;
};
Другие вопросы по тегам