在通用 lambda 中使用 `if constexpr` 访问成员类型需要两个分支都是格式正确的 - gcc vs clang

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

考虑两个具有不同成员类型别名的 struct

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

给定 template 上下文中的 T,我想获得 T::xT::y,具体取决于 T 是什么:

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++ 编译上面的代码,而 clang++ 产生这个错误:

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


clang++ 是否错误地拒绝了此代码?

请注意,clang++ 在通过通用 lambda l 删除间接时接受代码:

template <typename T>
auto s()
{
    if constexpr(std::is_same<T, foo>{}) { return typename T::x{}; }
    else                                 { return typename T::y{}; }
}

参见 Richard Smith's post 标准讨论:

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, [..]

也就是说,在实例化模板时,使用本地上下文(包括模板参数)部分实例化了通用 lambda 的主体;因此在 Clang 的实现中,T::xT::y 被直接替换,因为闭包类型可以传递到外部。这导致失败。正如@T.C 所指出的,代码可以被认为是错误的,不需要诊断,因为 s<foo> 的实例化产生了一个模板定义(闭包的定义),其第二个 if constexpr 分支没有格式正确的实例化。这解释了 Clang 和 GCC 的行为。

这归结为主要实现中的架构问题(另请参阅 ;GCC 显然不受此限制),所以如果 Core 认为您的代码很好,我会感到惊讶 -形成(毕竟,他们在通用 lambda 捕获的设计中考虑了这一点——请参阅链接的答案)。支持您的代码的 GCC 充其量只是一项功能(但可能有害,因为它使您能够编写依赖于实现的代码)。