Template alias的一个小陷阱

Bill Haoyuan Xing
4 min readJun 10, 2017

--

今天碰到一段行为很独特的代码,大意如下:

template<typename T>
struct A {
using U = T;
static U member;
};

template<typename T>
using X = typename A<T>::U;

template<typename T>
X<T> A<T>::member;

int main() {
return 0;
}

这段代码在gcc当中无法编译,报错如下:

test.cc:13:12: error: conflicting declaration ‘X<T> A<T>::member’
X<T> A<T>::member;
^~~~~~
test.cc:6:14: note: previous declaration as ‘A<T>::U A<T>::member’
static U member;
^~~~~~
test.cc:13:12: error: declaration of ‘A<T>::U A<T>::member’ outside of class is not definition [-fpermissive]
X<T> A<T>::member;
^~~~~~

然而clang/msvc编译这段代码却很正常。哪个编译器的行为正确呢?

从报错信息上来看,gcc认为X<T>和A<T>::U不是一个类型。而肉眼看上去这明明是一个类型。要解释这个行为,可以参考c++标准(17.5.7 temp.alias):

2 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

所以,template alias的equivalence是对特化之后的模板才有定义的。换言之,X<int>和A<int>::U是一个类型,然而X<T>和A<T>::U是不是一个类型,标准中并没有定义。然而问题是,在模板特化之前,编译器要先检查模板,确保这个模板定义是well-formed。于是,鉴于标准没有说怎么判定他们一样,g++就认为他们不一样,然后就发脾气了。

问题在于,为什么标准没有定义template alias的equivalence呢?因为边边角角的例子太多,标准委员会表示考虑不过来(#1286, #1979, #1558, #1520, #1244)。举两个例子来说,默认参数不同的别名该不该算作相同的类型(下文代码中的X和Y)?

template<typename T, typename U = char>
class X

template<typename T, typename U = int>
using typename Y = X

再比如说,如果有没用的参数怎么办(下文代码中的X<T>和Y<T,U>)?

template<typename T>
struct X {}

template<typename T, typename U = int>
using typename Y = X<T>

等等等等。所以就干脆不定义类型的相同了。

然而这样的结果就是开头那个看上去很直觉的程序还是编译不过,标准委员会计划定义一个简单一些的别名等价来回避这些复杂的问题,不过根据委员会历来的效率,感觉大概要2027年才能吵完这个问题吧。

Originally published at Hoppinglife.

--

--