Pablo Grube
Pablo Grube

Reputation: 71

Forwarding of return types of CRTP-derived class methods

I want to use "static polymorphism" via CRTP to do something like the following:

template <typename T>
struct Base
{
    double get_value() const { return ((T*)this)->_get_value(); }

protected:
    ~Base() {}
};

struct Derived1 : public Base<Derived1>
{
    double value;

    Derived1() : value(3.) {}

    const double& _get_value() const { return value; }
};

struct Derived2 : public Base<Derived2>
{
    double _get_value() const { return 5.; }
};

This works, but I'd also like that for the case of the object being instantiated as Derived1, get_value returns a const reference to the value instead of returning a copy. So in a way, a kind of "perfect forwarding" for return values.

I tried to declare get_value's return type like this:

template <typename T>
struct Base
{
    decltype(std::declval<T>()._get_value()) get_value() const { return ((T*)this)->_get_value(); }
    ...

but unsurprisingly GCC complained that this is an invalid use of incomplete type 'struct Derived1'.

Is there any way to go around this?

Thank you in advance! :)

Upvotes: 2

Views: 517

Answers (1)

dyp
dyp

Reputation: 39101

The reason why GCC is rejecting the proposed solution in the OP is that Base<Derived1> is being instantiated before Derived1. This instantiation consists of an instantiation of the signatures of all member functions, but it doesn't instantiate the function bodies themselves.

So we need to defer determination of the signature/return type of the member function until after Derived1 is visible.

One way to do this is by deferring determination of the return type via decltype(auto). This makes the return type dependent on the function body (which is not immediately instantiated). It's a C++14 feature, unfortunately.

template <typename T>
struct Base
{
    decltype(auto) get_value() const { return ((T*)this)->_get_value(); }

protected:
    ~Base() {}
};

See https://godbolt.org/z/r1T56n


This is probably covered by dcl.spec.auto and temp.inst.


Alternatively, even in C++11, you can postpone determination of the return type by turning the function into a function template, dependent on some dummy parameter if necessary:

template <typename T>
struct Base
{
    template<typename U=T>
    decltype(std::declval<U>()._get_value()) get_value() const {
        return ((T*)this)->_get_value();
    }

protected:
    ~Base() {}
};

Upvotes: 4

Related Questions