I was recently playing with CRTP when I came across something that surprised me when used with c++1y functions whose type is deduced. The following code works:
template<typename Derived>
struct Base
{
auto foo()
{
return static_cast<Derived*>(this)->foo_impl();
}
};
struct Derived:
public Base<Derived>
{
auto foo_impl()
-> int
{
return 0;
}
};
int main()
{
Derived b;
int i = b.foo();
(void)i;
}
I assumed that the return type from Base<Derived>::foo
was a decltype
of the expression returned, but if I modify the functio foo
like this:
auto foo()
-> decltype(static_cast<Derived*>(this)->foo_impl())
{
return static_cast<Derived*>(this)->foo_impl();
}
This code does not work anymore, I get the following error (from GCC 4.8.1):
||In instantiation of 'struct Base<Derived>':|
|required from here|
|error: invalid static_cast from type 'Base<Derived>* const' to type 'Derived*'|
||In function 'int main()':|
|error: 'struct Derived' has no member named 'foo'|
My questions are: Why doesn't it work? What could I possibly write to get the correct return type without relying on automatic return type deduction?
And, well... here is a live example.
In C++14, you can just use auto as a return type.
C++: “auto” return type deduction The “auto” keyword used to say the compiler: “The return type of this function is declared at the end”. In C++14, the compiler deduces the return type of the methods that have “auto” as return type. Restrictions: All returned values must be of the same type.
Since the compiler already has to deduce the return type from the return statement, in C++14, the auto keyword was extended to do function return type deduction. This works by using the auto keyword in place of the function's return type.
The definition of a member function of a class template is only implicitly instantiated when odr-used (or explicitly instantiated). That is, by deriving from Base<Derived>
, you do not implicitly instantiate the function body. Hence, the return type is still not deduced yet.
At the (*) point of instantiation, Derived
is complete, Derived::foo_impl
is declared, and the return type deduction can succeed.
(*) not "the", but "certain points of instantiation". There are several.
I assumed that the return type from
Base<Derived>::foo
was adecltype
of the expression returned, but if I modify the functionfoo
like this:
The trailing-return-type is part of the declaration of the member function; hence, it is part of the definition of the surrounding class, which is required to be instantiated when deriving from Base<Derived>
. At this point, Derived
is still incomplete, specifically Derived::foo_impl
has not been declared yet.
What could I possibly write to get the correct return type without relying on automatic return type deduction?
Now this is tricky. I'd say this is not very clearly defined in the Standard, e.g. see this question.
Here's an example that demonstrates that clang++3.4 does not find the members of Derived
inside Base<Derived>
:
template<typename Derived>
struct Base
{
auto foo() -> decltype( std::declval<Derived&>().foo_impl() )
{
return static_cast<Derived*>(this)->foo_impl();
}
};
declval
doesn't require a complete type, so the error message is that there's no foo_impl
in Derived
.
There's a hack, but I'm not sure if it's compliant:
template<typename Derived>
struct Base
{
template<class C = Derived>
auto foo() -> decltype( static_cast<C*>(this)->foo_impl() )
{
static_assert(std::is_same<C, Derived>{}, "you broke my hack :(");
return static_cast<Derived*>(this)->foo_impl();
}
};
I've discovered a solution, perhaps not very pretty, but I think it's pretty standard compliant.
It's been pointed out that this is quite limited as it assumes that foo_impl
can be implemented without accessing other parts of Derived or Base. Thanks @DyP. I've updated this answer with another approach.
Anyway, in terms of answering why the original code doesn't work, I defer to everyone else and to @Dyp's answer. I learned a lot, well described.
The basic issue, in layman's terms, (in my limited understanding!), is that when the compiler sees this line:
struct Derived: public Base<Derived>
it immediately wants/needs to know some/all information about Base<Derived>
even though it hasn't seen the following lines yet which define foo_impl
.
A solution is to move foo_impl
into another class called NotQuiteDerived
. Then Derived
inherits from this as well as from Base<...,...>
. This allows us to put foo_impl
before the introduction of Derived
. Then we need a second template type parameter in Base
. Anyway, the code can speak for itself ! :
I've changed this to a simpler, and maybe slightly better approach. Base
doesn't need to see all of Derived
, but the signature of foo_impl
. This can be passed along with the CRTP parameter.
Another approach now that is more flexible than the last, as it allows foo_impl
to have more access to Derived
and to act as if it was effectively a method of Derived
. We can declare foo_impl
as a friend of Derived
immediately before the struct Derived: ...
. This allows the implementation of foo_impl
to see the full definition of everything, and it allows the Base
to be given the return type of foo_impl
.
template<typename Derived, typename TypeOfTheFriendFunction>
struct Base
{
auto foo() -> typename std::function<TypeOfTheFriendFunction> :: result_type
{
return foo_impl_as_friend(static_cast<Derived*>(this) /*, any other args here*/);
}
};
struct Derived;
auto foo_impl_as_friend(Derived * This /*, any other args here*/) -> std::string;
struct Derived:
public Base<Derived, decltype(foo_impl_as_friend ) >
{
private:
void method_that_foo_impl_needs() { } // Just to demonstrate that foo_impl can act as part of Derived
friend decltype(foo_impl_as_friend) foo_impl_as_friend;
};
auto foo_impl_as_friend(Derived *This) -> std::string
{
This -> method_that_foo_impl_needs();
return "a string";
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With