为什么别名模板会提供相冲突的声明? [英] Why does alias template give a conflicting declaration?
问题描述
从Clang到g ++的一些C ++ 11代码的端口
template< class T>
使用value_t = typename T :: value_type;
模板< class>
struct S
{
using value_type = int;
static value_type const C = 0;
};
模板< class T>
value_t< S>>> // gcc error,typename S< T> :: value_type确实有效
const S< T> :: C;
int main()
{
static_assert(S< int> :: C == 0,);
$ / code>
给出了Clang(版本3.1到SVN中继)与任何g ++版。对于后者,我收到 这样的错误
prog.cc:13:13:错误:冲突声明'value_t< S< T> > S< <模板参数-1-1> > :: C'
const S< T> :: C;
^
prog.cc:8:29:note:以前的声明为'const value_type S< <模板参数-1-1> > :: C'
static value_type const C = 0;
^
prog.cc:13:13:错误:'const value_type S< <模板参数-1-1> > :: C'在类之外不是定义[-fpermissive] const S< T> :: C;
如果不是模板别名 value_t < ; S< T>>
我使用完整的 typename S< T> :: value_type
,那么 g ++也可以使用 。
/ strong>:模板别名是否应该与其基础表达式完全互换?这是一个g ++错误吗?
更新:Visual C ++也接受超类定义中的别名模板。
这个问题依赖于SFINAE。如果您将成员函数重写为 value_t< S< T>>< / code>,就像外部声明一样,GCC会很高兴地编译它:
模板< class T>
struct S
{
using value_type = int;
static const value_t< S< T>> C = 0;
};
模板< class T>
const value_t< S< T>> S< T> ::℃;
因为表达式现在是功能等效的。诸如替换失败之类的事情会在别名模板上发挥作用,但正如您所见,成员函数 value_type const C
不具有相同的原型为 value_t< S>> const S< T> :: C
。第一个不需要执行SFINAE,而第二个则需要。所以很显然,这两个声明都有不同的功能,因此GCC发脾气。
value_type )。
现在,从标准的眼睛看哪个是正确的?将alias-template的SFNIAE视为其声明功能的一部分,仍然是一个未解决的问题。引用 [temp.alias] / 2 :
当template-id引用别名模板的特化时,它等同于通过替换模板参数的模板参数而获得的关联类型在别名模板的type-id中。
换句话说,这两个是相同的:
模板< class T>
struct Alloc {/ * ... * /};
模板< class T>
使用Vec = vector< T,Alloc< T>>;
Vec< int> v;
vector< int,Alloc< int>> U;
Vec< int>
and vector< int,Alloc< int>>
是等价类型,因为在执行替换之后,两个类型最终都是 vector< int,Alloc< int>> ;
。请注意替换后的含义是否只有在所有模板参数都被替换为模板参数后才会检查等同性。也就是说,比较在 vector< T,Alloc< T>>
被<$ c替换时 T < $ c> int
来自 Vec< int>
。也许这就是Clang用 value_t< S< T>>
做的事情?但是接下来是 [temp.alias] / 3 中的以下引语:然而,如果template-id是依赖的,后续的模板参数替换仍然适用于template-id。 [例子:
模板< typename ...>使用void_t = void;
模板< typename T> void_t< typename T :: foo> F();
f< int>(); //错误,int没有嵌套类型foo
- 结束示例]
下面是问题:表达式具有,因此编译器需要检查替换是否正确。当为了执行模板参数替换而存在依赖关系(例如 typename T :: foo
)时,整个表达式的功能会发生变化,并且equivalence的定义会有所不同。例如,下面的代码不会编译(GCC和Clang):
struct X
{
模板< typename T>
auto foo(T) - > std :: enable_if_t< sizeof(T)== 4>;
};
模板< typename T>
auto X :: foo(T) - >无效
{}
因为外层 foo
的原型在功能上与内部原型不同。做 auto X :: foo(T) - > std :: enable_if_t< sizeof(T)== 4>
代替使代码编译正常。这是因为返回类型 foo
是一个依赖于 sizeof(T)== 4
,所以在模板替换之后,其原型可能与其每个实例不同。然而, auto X :: foo(T) - > void
的返回类型永远不会不同,它与 X
中的声明冲突。这与你的代码发生的问题是一样的。所以GCC在这种情况下似乎是正确的。
The port of some C++11 code from Clang to 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, "");
}
gives different behavior for Clang (versions 3.1 through SVN trunk) versus for any g++ version. For the latter I get errors 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;
If instead of the template alias value_t<S<T>>
I use the full typename S<T>::value_type
then g++ also works.
Question: aren't template aliases supposed to be completely interchangeable with their underlying expression? Is this a g++ bug?
Update: Visual C++ also accepts the alias template in the out-of-class definition.
解决方案 The problem relies on SFINAE. If you rewrite your member function to be value_t<S<T>>
, like the outside declaration, then GCC will happily compile it:
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;
Because the expression is now functionally equivalent. Things like substitution failure come into play on alias-templates, but as you see, the member function value_type const C
doesn't have the same "prototype" as value_t<S<T>> const S<T>::C
. First one doesn't have to perform SFINAE, whereas the second one requires it. So clearly both declarations have different functionality, hence GCC's tantrum.
Interestingly, Clang compiles it without a sign of abnormality. I assume it just so happens that the order of Clang's analyses are reversed, compared to GCC's. Once the alias-template expression is resolved and fine (i.e. it is well-formed), clang then compares both declarations and check it they are equivalent (which in this case they are, given both expressions resolve to value_type
).
Now, which one is correct from the standard's eyes? It's still an unresolved issue to whether consider alias-template's SFNIAE as part of its declaration's functionality. Quoting [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.
In other words, these two are equivalent:
template<class T>
struct Alloc { /* ... */ };
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
vector<int, Alloc<int>> u;
Vec<int>
and vector<int, Alloc<int>>
are equivalent types, because after substitution is performed, both types end up being vector<int, Alloc<int>>
. Note how "after substitution" means that the equivalence is only checked once all template arguments are replaced with the template parameters. That is, comparison starts when T
in vector<T, Alloc<T>>
is replaced with int
from Vec<int>
. Maybe that's what Clang is doing with value_t<S<T>>
? But then there's the following quote from [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]
Here's the problem: the expression has to be well-formed, so the compiler needs to check whether the substitution is fine. When there is a dependence in order to perform template argument substitution (e.g. typename T::foo
), the functionality of the whole expression changes, and the definition of "equivalence" differs. For example, the following code won't compile (GCC and Clang):
struct X
{
template <typename T>
auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};
template <typename T>
auto X::foo(T) -> void
{}
Because the outer foo
's prototype is functionally different from the inner one. Doing auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
instead makes the code compile fine. It's so because the return type of foo
is an expression that is dependent on the result of sizeof(T) == 4
, so after template substitution, its prototype might be different from each instance of it. Whereas, auto X::foo(T) -> void
's return type is never different, which conflicts with the declaration inside X
. This is the very same issue that's happening with your code. So GCC seems to be correct in this case.
这篇关于为什么别名模板会提供相冲突的声明?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!