Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does alias template give a conflicting declaration?

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.

like image 994
TemplateRex Avatar asked Jan 13 '17 19:01

TemplateRex


1 Answers

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.

like image 96
Mário Feroldi Avatar answered Nov 08 '22 04:11

Mário Feroldi