Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template aliases and dependent names

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?

like image 900
K-ballo Avatar asked Oct 02 '22 18:10

K-ballo


1 Answers

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.

like image 114
aschepler Avatar answered Oct 12 '22 09:10

aschepler