Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CRTP fails w/ decltype

template<typename T> struct A {
    auto func() -> decltype(T::func()) {
        return T::func();
    }
};
class B : public A<B> {
    void func() {
    }
};

Seems pretty simple to me. But MSVC fails to compile.

visual studio 2010\projects\temp\temp\main.cpp(4): error C2039: 'func' : is not a member of 'B'
visual studio 2010\projects\temp\temp\main.cpp(8) : see declaration of 'B'
visual studio 2010\projects\temp\temp\main.cpp(8) : see reference to class template instantiation 'A<T>' being compiled
          with
          [
              T=B
          ]
visual studio 2010\projects\temp\temp\main.cpp(4): error C3861: 'func': identifier not found

Even though the compiler will happily accept calling the function. The below sample compiles fine.

template<typename T> struct A {
    void func() {
        return T::func();
    }
};
class B : public A<B> {
    void func() {
    }
};

I've got the same issue trying to use any types from the template argument.

template<typename T> struct A {
    typedef typename T::something something;
};
class B : public A<B> {
    typedef char something;
};

visual studio 2010\projects\temp\temp\main.cpp(4): error C2039: 'something' : is not a member of 'B'

Whereas class B clearly defines a type called "something". The compiler is perfectly happy to call functions on an object of type T, T& or T*, but I can't seem to access any types from T.

like image 589
Puppy Avatar asked Nov 19 '10 14:11

Puppy


3 Answers

You are trying to use T::func before it was declared. That's why the compiler shouts at you. Notice that when you derive from a class, the class is generated if it comes from a class template. And the implicit generation of the class (which is called implicit instantiation) necessiates the generation of declarations for all its members (so the compiler knows the sizeof value of the class, and can perform lookup into it).

So it also instantiates the declaration auto func() -> decltype(T::func()) and surely fails here.

like image 146
Johannes Schaub - litb Avatar answered Nov 15 '22 08:11

Johannes Schaub - litb


There seem to be several issues with your code, one of which looks like a VS10 bug.

  1. You're calling T::func() from A without casting A to T, this is needed as part of CRTP since A doesn't derive from T. -- FixL return static_cast<T*>(this)->func();
  2. What you're passing to decltype looks like a static function invocation while func is in fact an instance function. Since decltype doesn't actually run the function you should do something like this decltype(static_cast<T*>(nullptr)->func())
  3. func is private in B and can't be called from A -- Fix: change A to be a struct
  4. This looks like a bug in VS10, even after all these fixes I get an error that you're trying to use an undefined class B in the decltype.

As a workaround can you refactor func out into a base class? (now we need two template parameters, one for casting to and one for decltype thus creating a new idiom CRTPEX)

struct Base { 
    void func() { }
};

template<typename T, typename U> struct A {
    auto func() -> decltype(static_cast<T*>(nullptr)->func()) {
        return static_cast<U*>(this)->func();
    }
};


struct B : public A<Base, B>, public Base {
};

I see that g++ also chokes on this decltype can anyone confirm that this is a defect? If so I will open a bug for Microsoft. It is my understanding that the following code is valid but neither g++ nor VC10 compile it.

template<typename T> struct A {
    auto func() -> decltype(static_cast<T*>(nullptr)->func()) {
        return static_cast<T*>(this)->func();
    }
};

struct B : public A<B> {
    void func() {}
};
like image 33
Motti Avatar answered Nov 15 '22 07:11

Motti


First, I think that the close-to-proper code is:

template<typename T> struct A {
    auto func()
     -> decltype(static_cast<T*>(this)->func()) 
    {
        return static_cast<T*>(this)->func();
    }
};
class B : public A<B> {
    void func(){
    }
};

As Motti pointed out. However that still fails, and I think for the reason that the return type of the base has to be known when B is declated to inherit from A<B>, but since B is not defined yet, it becomes a chicken and egg problem.

However, it may be finally be possible in C++1y by using simply auto (without decltype), I tried with gcc-4.8.2

template<typename T> struct A {
    auto func()
    //c++1y// -> decltype(static_cast<T*>(this)->func()) 
    {
        return static_cast<T*>(this)->func();
    }
};
class B : public A<B> {
    void func(){
    }
};

This compiles (c++ -std=c++1y) and runs:

int main(){
  B b; b.func();
}

Two disclaimers: I don't know why this works. I don't know how standard it is.

like image 1
alfC Avatar answered Nov 15 '22 07:11

alfC