While thinking how CRTP can be improved in C++11, I ended with the following code:
template <typename Derived, typename Delayer>
struct derived_value_type
{
typedef typename Derived::value_type type;
};
template <typename Derived>
struct base
{
template <typename Delayer = void>
typename derived_value_type<Derived, Delayer>::type
foo(){ return {}; }
};
struct derived : base<derived>
{
typedef int value_type;
};
#include <iostream>
#include <typeinfo>
int main()
{
derived d;
auto bar = d.foo();
std::cout << typeid(bar).name() << ':' << bar << std::endl;
}
I believe the previous code to be standard conformant, and it compiles and works with the major compilers (resulting in i:0
). However, when I use a template alias instead, I get a compilation error due to derived
being incomplete:
template <typename Derived, typename Delayer>
using derived_value_type = typename Derived::value_type;
/*...*/
template <typename Delayer = void>
derived_value_type<Derived, Delayer>
foo(){ return {}; }
Is this a compiler bug, or does the fact that the compiler can determinate that there is no real dependency with Delayer
mean that the template alias is not a dependent type? Where is this specified in the standard?
Class templates and function templates are instantiated, but alias templates are simply substituted. And by getting rid of the member name type
, you lose a chance at invoking the dependent name lookup rules.
[N3285] 14.5.7p2:
When a template-id refers to the specialization of an alias template, it is equivalent to the associated type obtained by substitution of its template-arguments for the template-parameters in the type-id of the alias template.
So in the first case, you have:
The definition of struct derived
requires the implicit instantiation of base<derived>
. During this instantiation, we discover base<derived>
has a member function template:
template <typename Delayer=void>
typename derived_value_type<derived, Delayer>::type foo();
The return type is dependent, so type
is not yet looked up, and no specialization of derived_value_type
is instantiated. The instantiation finishes, and base<derived>
and derived
are both now complete types.
In main
, the expression d.foo()
requires the implicit instantiation of base<derived>::foo<void>()
. Now the name typename derived_value_type<derived, void>::type
is looked up, instantiating derived_value_type<derived, void>
along the way. The return type is found to be int
.
In the second case, derived_value_type
is not a dependent name, so is bound to your alias template declaration at the definition of template base<D>
. The compiler could do the alias substitution either at the template definition or during each instantiation of the class, but either way you get a class template equivalent to:
template <typename Derived>
struct base
{
template <typename Delayer = void>
typename Derived::value_type
foo(){ return {}; }
};
The definition of struct derived
requires the implicit instantiation of base<derived>
. During this instantiation, we discover base<derived>
has a member function template:
template <typename Delayer=void>
typename derived::value_type foo();
But derived::value_type
is not dependent, and derived
is an incomplete type, so the code is ill-formed.
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