Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No type named 'type' in CTRP derived class

I've been experimenting with the Curiously Recurring Template Pattern for a generic single-argument functor and have two implementations: one using a template template parameter which works and a second where I try to access the derived Functor::type in the interface class. In the latter example, the compiler (gcc 5.4.0) reports

error: no type named 'type' in 'struct Cube< double >'

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!)

I then try to compile with T=double in main() of the following two classes:

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

Can the FunctorInterface_2/Cube example be modified to work, or is it necessary for the interface class to be templated on T as in the first example? Thanks!

EDIT: Using gcc -std=c++14, I can get the second example to compile and run by using auto return and argument types in FunctorInterface_1::operator(), however, as I understand, auto argument types are not part of the C++14 standard.

EDIT 2: Well I feel a bit thick. I just realized that I could template FunctorInterface_1::operator() on a new parameter, however, for the application I have in mind, I would really like my base class to be able to access types defined in the derived class.

like image 994
Greg von Winckel Avatar asked Aug 21 '17 16:08

Greg von Winckel


4 Answers

When the line

using Ftype = typename Functor::type;

is processed in the base class, the definition of Functor is not available. Hence, you can't use Functor::type.

One way to get around this limitation is to define a traits class.

// 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

Working code: https://ideone.com/C1L4YW

like image 89
R Sahu Avatar answered Nov 14 '22 23:11

R Sahu


Note: This question has already been answered by @r-sahu, but I'd like to elaborate on this and address clang's output specifically.

The problem can be demonstrated on a much smaller code sample: (@vtt suggested something similar)

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

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

Compiling that with clang will result in completely misleading error message: (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's error message is a little more helpful: (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> {
      |        ^

As suggested in the accepted answer, implementing a trait type fixes the issue:

// 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;
};
like image 39
asynts Avatar answered Sep 23 '22 00:09

asynts


Your code could be simplified to

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;

It does not work because at the point of Base instantiation Derived class is not complete, and compiler is not aware of Derived::type existence yet.

like image 3
user7860670 Avatar answered Nov 15 '22 01:11

user7860670


You have to understand that when you instantiate Cube<T> FunctionInterface_2<Cube<T>> gets instantiated first. This means that Cube<T> is an incomplete type while this is happening.
So when the compiler gets to the line using Ftype = typename Functor::type; Functor is incomplete and you cannot access any of its nested types.

In your case you can change FunctionInterface_2 to:

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

So now accessing information about Functor is delayed until you call the operator() from FunctionInterface_2 at which point FunctionInterface_2 and Cube are fully instantiated.

like image 1
SirGuy Avatar answered Nov 14 '22 23:11

SirGuy