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.
Best Answer
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: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" asvalue_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:
In other words, these two are equivalent:
Vec<int>
andvector<int, Alloc<int>>
are equivalent types, because after substitution is performed, both types end up beingvector<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 whenT
invector<T, Alloc<T>>
is replaced withint
fromVec<int>
. Maybe that's what Clang is doing withvalue_t<S<T>>
? But then there's the following quote from [temp.alias]/3: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):Because the outer
foo
's prototype is functionally different from the inner one. Doingauto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
instead makes the code compile fine. It's so because the return type offoo
is an expression that is dependent on the result ofsizeof(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 insideX
. This is the very same issue that's happening with your code. So GCC seems to be correct in this case.