Consider two struct
s 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{}; }
}
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With