为什么 std::visit 在一个不满足的概念中导致 gcc 编译错误

Why does std::visit in an unsatisfied concept cause a compile error in gcc

此代码:

#include <concepts>
#include <string>
#include <variant>

struct any_callable {
public:
    template<typename T>
    void operator()(T&&) {}
};

template<typename V>
concept is_variant = requires(V v) { std::visit(any_callable{}, v); };

int main() {
    constexpr bool wrapped = is_variant<std::string>;
}

不能在 gcc 11 下编译。它给出了一堆关于 valueless_by_exception 之类的错误。但是,它确实在 msvc (godbolt) 下编译。现在,据我了解概念,在这种情况下,如果它通常无法编译,它将衰减为 false,否则为 true。 msvc 的行为似乎支持这一点。

所以:这是 gcc 中的错误吗,这是 msvc 的一个非标准功能,and/or 是我的代码有误吗?

就是这个:

template<typename V>
concept is_variant = requires(V v) { std::visit(any_callable{}, v); };

完全有效是最近的更改,是 P2162 的结果。为了使此检查生效,您需要 std::visit 成为通常所说的“SFINAE 友好”。也就是说:它必须以某种方式限制“变体”,这样如果 V 是一个变体,它就可以工作,如果 V 不是一个变体,那么 visit 从重载设置使得此调用是无效表达式(因此 concept 可以被拒绝)。

但是,在 P2162 之前,std::visit 上没有 约束。这不是 SFINAE 友好的:调用要么有效要么格式不正确。正是这篇论文 添加了 您传递给它的类型是变体(或从一个变体继承)的约束。这就是为什么您会看到您看到的错误:对 visit 的调用失败了,但不是以一种对 concept 检查友好的方式。

Post-P2162(正如论文指出的那样,MSVC 已经实现,但 libstdc++ 没有),您的检查将有效。

但我们可以在 C++20 中更轻松地做到这一点,而不必进入变体机制——通过以与论文相同的方式直接检查变体:

template<typename V>
concept is_variant = requires(V v) {
    []<typename... Ts>(std::variant<Ts...> const&){}(v);
};

如果 vvariant 或继承自 variant,则 lambda 可通过 v 调用。否则,它的格式不正确。这比通过 visit.

更直接