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.
来源:https://stackoverflow.com/questions/41642359/why-does-alias-template-give-a-conflicting-declaration