Why does alias template give a conflicting declaration?

前端 未结 1 1898
离开以前
离开以前 2021-01-07 18:12

The port of some C++11 code from Clang to g++

template
using value_t = typename T::value_type;

template
struct S
{
    using val         


        
1条回答
  •  小鲜肉
    小鲜肉 (楼主)
    2021-01-07 18:40

    The problem relies on SFINAE. If you rewrite your member function to be value_t>, like the outside declaration, then GCC will happily compile it:

    template
    struct S
    {
        using value_type = int;
        static const value_t> C = 0;
    };
    
    template 
    const value_t> S::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> const S::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
    struct Alloc { /* ... */ };
    
    template
    using Vec = vector>;
    
    Vec v;
    vector> u;
    

    Vec and vector> are equivalent types, because after substitution is performed, both types end up being vector>. 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> is replaced with int from Vec. Maybe that's what Clang is doing with value_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 using void_t = void;
    template void_t f();
    f(); // 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 
        auto foo(T) -> std::enable_if_t;
    };
    
    template 
    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 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.

    0 讨论(0)
提交回复
热议问题