Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using decltype in a late specified return in CRTP base class

I'm trying to use decltype in the late specified return of a member function in a CRTP base class and it's erroring with: invalid use of incomplete type const struct AnyOp<main()::<lambda(int)> >.

template<class Op>
struct Operation
{
    template<class Foo>
    auto operator()(const Foo &foo) const ->
        typename std::enable_if<is_foo<Foo>::value,
                                decltype(static_cast<const Op*>(nullptr)->call_with_foo(foo))>::type     
    {
        return static_cast<const Op*>(this)->call_with_foo(foo);
    }
};


template<class Functor>
struct AnyOp : Operation<AnyOp<Functor> >
{
    explicit AnyOp(Functor func) : func_(func) {}

    template<class Foo>
    bool call_with_foo(const Foo &foo) const
    {
        //do whatever
    }

  private:
    Functor func_;
};

I'm basically trying to move all of the sfinae boiler plate into a base class so I don't need to repeat it for every Operation that I create (currently each operation has 6 different calls and there are ~50 operations so there is quite a lot of repetition with the enable_if's).

I've tried a solution which relied on overloading but one of the types which may be passed is anything that's callable(this can be a regular functor from C++03 or a C++0x lambda) which I bound to a std::function, unfortunately, the overhead from std::function, although very minimal, actually makes a difference in this application.

Is there a way to fix what I currently have or is there a better solution all together to solve this problem?

Thanks.

like image 744
Tim Avatar asked Feb 20 '11 23:02

Tim


2 Answers

You are, as another answer describes already, trying to access a member of a class in one of the class' base class. That's going to fail because the member is yet undeclared at that point.

When it instantiates the base class, it instantiates all its member declarations, so it needs to know the return type. You can make the return type be dependent on Foo, which makes it delay the computation of the return type until Foo is known. This would change the base class like the following

// ignore<T, U> == identity<T>
template<typename T, typename Ignore> 
struct ignore { typedef T type; };

template<class Op>
struct Operation
{
    template<class Foo>
    auto operator()(const Foo &foo) const ->
        typename std::enable_if<is_foo<Foo>::value,
           decltype(static_cast<typename ignore<const Op*, Foo>::type>(nullptr)->call_with_foo(foo))>::type     
    {
        return static_cast<const Op*>(this)->call_with_foo(foo);
    }
};

This artificially makes the static_cast cast to a type dependent on Foo, so it does not immediately require a complete Op type. Rather, the type needs to be complete when operator() is instantiated with the respective template argument.

like image 127
Johannes Schaub - litb Avatar answered Oct 22 '22 02:10

Johannes Schaub - litb


You are trying to refer to a member of a class from one of its own base classes, which will fail since the class's body doesn't exist within its base class. Can you pass the logic for computing the return type of call_with_foo as a metafunction to the base class? Is that logic ever going to be complicated?

Another option, depending on how much flexibility you have in changing your class hierarchy (and remember that you have template typedefs), is to have the wrapper inherit from the implementation class rather than the other way around. For example, you can write a AddParensWrapper<T> that inherits from T and has operator() that forwards to T::call_with_foo. That will fix the dependency problem.

like image 43
Jeremiah Willcock Avatar answered Oct 22 '22 04:10

Jeremiah Willcock