Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing member type with `if constexpr` inside generic lambda requires both branches to be well-formed - gcc vs clang

Consider two structs with different member type aliases:

struct foo { using x = int;   };
struct bar { using y = float; };

Given a T in a template context, I want to get either T::x or T::y depending on what T is:

template <typename T>
auto s()
{
    auto l = [](auto p) 
    {
        if constexpr(p) { return typename T::x{}; }
        else            { return typename T::y{}; }
    };

    return l(std::is_same<T, foo>{});
}

int main() 
{ 
    s<foo>(); 
}

g++ compiles the code above, while clang++ produces this error:

error: no type named 'y' in 'foo'
        else            { return typename T::y{}; }
                                 ~~~~~~~~~~~~^
note: in instantiation of function template specialization 's<foo>' requested here
    s<foo>();
    ^

on godbolt.org, with conformance viewer


Is clang++ incorrectly rejecting this code?

Note that clang++ accepts the code when removing the indirection through the generic lambda l:

template <typename T>
auto s()
{
    if constexpr(std::is_same<T, foo>{}) { return typename T::x{}; }
    else                                 { return typename T::y{}; }
}
like image 954
Vittorio Romeo Avatar asked Jun 30 '17 09:06

Vittorio Romeo


1 Answers

See Richard Smith's post on std-discussion:

In the implementation I'm familiar with [i.e. Clang], a key problem is that the lexical scopes used while processing a function definition are fundamentally transient, which means that delaying instantiation of some portion of a function template definition is hard to support. Generic lambdas don't suffer from a problem here, because the body of the generic lambda is instantiated with the enclosing function template, [..]

That is, generic lambdas' bodies are partially instantiated using the local context (including template arguments) when the template is instantiated; thus under Clang's implementation, T::x and T::y are substituted directly, since the closure type could be passed outside. This leads to the failure. As pointed out by @T.C., the code can be considered ill-formed, no diagnostic required, as the instantiation of s<foo> yields a template definition (that of the closure) whose second if constexpr branch has no well-formed instantiations. This explains the behaviour of both Clang and GCC.

This boils down to an architectural issue in a major implementation (see also this answer; GCC apparently doesn't suffer from this limitation), so I'd be surprised if Core would deem your code well-formed (after all, they accounted for this in the design of generic lambda captures--see the linked answer). GCC supporting your code is, at best, a feature (but probably harmful, since it enables you to write implementation-dependent code).

like image 118
Columbo Avatar answered Nov 04 '22 17:11

Columbo