为什么别名模板会给出冲突的声明?
Why does alias template give a conflicting declaration?
部分 C++11 代码从 Clang 到 g++ 的移植
template<class T>
using value_t = typename T::value_type;
template<class>
struct S
{
using value_type = int;
static value_type const C = 0;
};
template<class T>
value_t<S<T>> // gcc error, typename S<T>::value_type does work
const S<T>::C;
int main()
{
static_assert(S<int>::C == 0, "");
}
Clang(版本 3.1 通过 SVN 主干)与任何 g++ 版本不同。对于后者,我得到错误 like this
prog.cc:13:13: error: conflicting declaration 'value_t<S<T> > S< <template-parameter-1-1> >::C'
const S<T>::C;
^
prog.cc:8:29: note: previous declaration as 'const value_type S< <template-parameter-1-1> >::C'
static value_type const C = 0;
^
prog.cc:13:13: error: declaration of 'const value_type S< <template-parameter-1-1> >::C' outside of class is not definition [-fpermissive] const S<T>::C;
如果我使用完整的 typename S<T>::value_type
而不是模板别名 value_t<S<T>>
,则 g++ also works。
问题:模板别名不应该与其底层表达式完全互换吗?这是 g++ 错误吗?
更新: Visual C++ 也接受class定义之外的别名模板。
问题依赖SFINAE。如果你把你的成员函数重写成value_t<S<T>>
,就像外面的声明一样,那么GCC会很乐意编译它:
template<class T>
struct S
{
using value_type = int;
static const value_t<S<T>> C = 0;
};
template<class T>
const value_t<S<T>> S<T>::C;
因为表达式现在在功能上 是等效的。诸如 substitution failure 之类的事情在别名模板上发挥作用,但如您所见,成员函数 value_type const C
没有与 value_t<S<T>> const S<T>::C
相同的“原型” .第一个不必执行 SFINAE,而第二个则需要它。很明显,这两个声明具有不同的功能,因此 GCC 大发雷霆。
有趣的是,Clang编译它没有任何异常迹象。我认为与 GCC 相比,Clang 的分析顺序恰好相反。一旦别名模板表达式被解析并且很好(即它是格式正确的),clang 就会比较两个声明并检查它们是否等效(在这种情况下它们是等效的,因为两个表达式都解析为 value_type
)。
现在,在标准的眼里,哪一个是正确的?是否将别名模板的 SFNIAE 视为其声明功能的一部分仍然是一个未解决的问题。引用 [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<class T>
struct Alloc { /* ... */ };
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
vector<int, Alloc<int>> u;
Vec<int>
和 vector<int, Alloc<int>>
是等价的类型,因为在执行替换后,两种类型最终都是 vector<int, Alloc<int>>
。请注意“替换后”是如何表示仅在所有模板参数都替换为模板参数后才检查等价性。也就是说,当 vector<T, Alloc<T>>
中的 T
被替换为 Vec<int>
中的 int
时,比较开始。也许这就是 Clang 对 value_t<S<T>>
所做的事情?但是 [temp.alias]/3 中有以下引述:
However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [Example:
template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo
— end example]
这里的问题是:表达式 必须 是合式的,因此编译器需要检查替换是否正确。当为了执行模板参数替换而存在依赖时(例如 typename T::foo
),整个表达式的功能发生变化,“等价”的定义也不同。例如,以下代码将无法编译(GCC 和 Clang):
struct X
{
template <typename T>
auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};
template <typename T>
auto X::foo(T) -> void
{}
因为外部foo
的原型在功能上与内部的不同。相反,执行 auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
可以使代码编译正常。之所以如此,是因为foo
的return类型是依赖于sizeof(T) == 4
结果的表达式,所以模板替换后,其原型可能与它的每个实例都不同。然而,auto X::foo(T) -> void
的 return 类型永远不会不同,这与 X
中的声明冲突。这与您的代码中发生的问题完全相同。所以 GCC 在这种情况下似乎是正确的。
部分 C++11 代码从 Clang 到 g++ 的移植
template<class T>
using value_t = typename T::value_type;
template<class>
struct S
{
using value_type = int;
static value_type const C = 0;
};
template<class T>
value_t<S<T>> // gcc error, typename S<T>::value_type does work
const S<T>::C;
int main()
{
static_assert(S<int>::C == 0, "");
}
Clang(版本 3.1 通过 SVN 主干)与任何 g++ 版本不同。对于后者,我得到错误 like this
prog.cc:13:13: error: conflicting declaration 'value_t<S<T> > S< <template-parameter-1-1> >::C' const S<T>::C; ^ prog.cc:8:29: note: previous declaration as 'const value_type S< <template-parameter-1-1> >::C' static value_type const C = 0; ^ prog.cc:13:13: error: declaration of 'const value_type S< <template-parameter-1-1> >::C' outside of class is not definition [-fpermissive] const S<T>::C;
如果我使用完整的 typename S<T>::value_type
而不是模板别名 value_t<S<T>>
,则 g++ also works。
问题:模板别名不应该与其底层表达式完全互换吗?这是 g++ 错误吗?
更新: Visual C++ 也接受class定义之外的别名模板。
问题依赖SFINAE。如果你把你的成员函数重写成value_t<S<T>>
,就像外面的声明一样,那么GCC会很乐意编译它:
template<class T>
struct S
{
using value_type = int;
static const value_t<S<T>> C = 0;
};
template<class T>
const value_t<S<T>> S<T>::C;
因为表达式现在在功能上 是等效的。诸如 substitution failure 之类的事情在别名模板上发挥作用,但如您所见,成员函数 value_type const C
没有与 value_t<S<T>> const S<T>::C
相同的“原型” .第一个不必执行 SFINAE,而第二个则需要它。很明显,这两个声明具有不同的功能,因此 GCC 大发雷霆。
有趣的是,Clang编译它没有任何异常迹象。我认为与 GCC 相比,Clang 的分析顺序恰好相反。一旦别名模板表达式被解析并且很好(即它是格式正确的),clang 就会比较两个声明并检查它们是否等效(在这种情况下它们是等效的,因为两个表达式都解析为 value_type
)。
现在,在标准的眼里,哪一个是正确的?是否将别名模板的 SFNIAE 视为其声明功能的一部分仍然是一个未解决的问题。引用 [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<class T>
struct Alloc { /* ... */ };
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
vector<int, Alloc<int>> u;
Vec<int>
和 vector<int, Alloc<int>>
是等价的类型,因为在执行替换后,两种类型最终都是 vector<int, Alloc<int>>
。请注意“替换后”是如何表示仅在所有模板参数都替换为模板参数后才检查等价性。也就是说,当 vector<T, Alloc<T>>
中的 T
被替换为 Vec<int>
中的 int
时,比较开始。也许这就是 Clang 对 value_t<S<T>>
所做的事情?但是 [temp.alias]/3 中有以下引述:
However, if the template-id is dependent, subsequent template argument substitution still applies to the template-id. [Example:
template<typename...> using void_t = void; template<typename T> void_t<typename T::foo> f(); f<int>(); // error, int does not have a nested type foo
— end example]
这里的问题是:表达式 必须 是合式的,因此编译器需要检查替换是否正确。当为了执行模板参数替换而存在依赖时(例如 typename T::foo
),整个表达式的功能发生变化,“等价”的定义也不同。例如,以下代码将无法编译(GCC 和 Clang):
struct X
{
template <typename T>
auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};
template <typename T>
auto X::foo(T) -> void
{}
因为外部foo
的原型在功能上与内部的不同。相反,执行 auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
可以使代码编译正常。之所以如此,是因为foo
的return类型是依赖于sizeof(T) == 4
结果的表达式,所以模板替换后,其原型可能与它的每个实例都不同。然而,auto X::foo(T) -> void
的 return 类型永远不会不同,这与 X
中的声明冲突。这与您的代码中发生的问题完全相同。所以 GCC 在这种情况下似乎是正确的。