No type named 'type' in CTRP derived class

后端 未结 4 561
猫巷女王i
猫巷女王i 2020-12-31 11:22

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 para

相关标签:
4条回答
  • 2020-12-31 12:03

    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;
    };
    
    0 讨论(0)
  • 2020-12-31 12:08

    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.

    0 讨论(0)
  • 2020-12-31 12:15

    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

    0 讨论(0)
  • 2020-12-31 12:23

    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.

    0 讨论(0)
提交回复
热议问题