在考虑如何在 C ++ 11 中改进 CRTP 时,我结束了以下代码:
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:0
)。但是,当我使用模板别名时,由于derived
不完整而导致编译错误:
template <typename Derived, typename Delayer>
using derived_value_type = typename Derived::value_type;
/*...*/
template <typename Delayer = void>
derived_value_type<Derived, Delayer>
foo(){ return {}; }
这是编译器错误,还是编译器可以确定与Delayer
没有真正依赖关系的事实意味着模板别名不是依赖类型?标准中指定的是哪里?
答案 0 :(得分:4)
实例化类模板和函数模板,但简单地替换别名模板。通过删除成员名称type
,您将失去调用依赖名称查找规则的机会。
[N3285] 14.5.7p2:
当 template-id 引用别名模板的特化时,它等同于通过将 template-arguments 替换为 type-id 中的> template-parameters 。
所以在第一种情况下,你有:
struct derived
的定义需要base<derived>
的隐式实例化。在此实例化期间,我们发现base<derived>
具有成员函数模板:
template <typename Delayer=void>
typename derived_value_type<derived, Delayer>::type foo();
返回类型是依赖的,因此尚未查找type
,并且未实例化derived_value_type
的特化。实例化完成,base<derived>
和derived
现在都是完整类型。
在main
中,表达式d.foo()
需要隐式实例化base<derived>::foo<void>()
。现在查找名称typename derived_value_type<derived, void>::type
,沿途实例化derived_value_type<derived, void>
。返回类型为int
。
在第二种情况下,derived_value_type
不是依赖名称,因此在模板base<D>
的定义中绑定到别名模板声明。编译器可以在模板定义或类的每个实例化期间进行别名替换,但无论哪种方式,您都可以获得等同于的类模板:
template <typename Derived>
struct base
{
template <typename Delayer = void>
typename Derived::value_type
foo(){ return {}; }
};
struct derived
的定义需要base<derived>
的隐式实例化。在此实例化期间,我们发现base<derived>
具有成员函数模板:
template <typename Delayer=void>
typename derived::value_type foo();
但derived::value_type
不依赖,derived
是不完整的类型,因此代码格式不正确。