在 msvc 和 clang 中导致编译器错误的概念?

Concepts causing compiler bugs in msvc and clang?

我有以下代码:

#include <variant>

template <typename T>
struct S{};

using Var = std::variant<S<int>, S<float>>;

template <typename T>
concept VariantMember = requires(Var var) { std::get<T>(var); };

void foo(VariantMember auto x) {}
void foo(auto x) {}

void bar()
{
    foo(S<int>{});
    foo(S<char>{});
}

MSVC 错误

error C2338: get(variant<Types...>&) requires T to occur exactly once in Types. (N4835 [variant.get]/5)

在这种情况下,只有当我在变体 S 中使用同一模板的两个实例时才会发生这种情况。所以先是 std::get 出乎意料的错误,然后概念没有失败而是直接出错了。有趣的是,如果我在概念之外使用 std::get,例如:

std::get<S<int>>(Var{ S<int>{} });

代码编译正常。

Clang 似乎忽略了这个概念,在这两种情况下总是选择 foo 的第一个重载。

godbolt

这些真的是错误还是我搞砸了?

首先,您的 S 模板是噪音。正在删除它。

using Var = std::variant<int, float>;

template <typename T>
concept VariantMember = requires(Var var) { std::get<T>(var); };

template <int = 1>
void foo(VariantMember auto x) {}

template <int = 2>
void foo(auto x) {}

void bar()
{
    foo(int{});
    foo(char{});
}

我们得到了同样的错误。如果我注释掉 char 案例,它在 MSVC 中编译得很好。

std::get<char>(Var{}) 不需要对 SFINAE 友好。 std::get 在此处的 MSVC 中可能(并且确实)会失败。

在这个版本中,一旦我删除了发生硬错误的 char,clang 和 msvc 都会调用 int=1 重载。

我们如何处理硬错误?写一个没有的getsafe_get:

template<class T, class...Ts>
requires (1 == (std::is_same_v<T, Ts>+...))
T& safe_get( std::variant<Ts...>& var ) 
  return std::get<T>(var);
}
template<class T, class...Ts>
requires (1 == (std::is_same_v<T, Ts>+...))
T const& safe_get( std::variant<Ts...> const& var ) 
  return std::get<T>(var);
}
// ...
template <typename T>
concept VariantMember = requires(Var var) {
  {safe_get<T>(var)};
};

(我添加并声明 1,这给了我“匹配且仅一次”)。

然后我们 return 您的 S 模板和 everything works.

explained the issue: std::get mandates 该类型出现在变体中。它不是 SFINAE 友好的(即它不是约束)。因此,您的 VariantMember<T> 概念并没有真正检查任何内容:它要么是 true 要么是错误的。

相反,我们必须以不同的方式编写概念。使用 Boost.Mp11,这是一个简短的一行(一如既往):

template <typename T>
concept VariantMember = mp_contains<Var, T>::value;

我不清楚你是否需要检查 T 是否只是变体的一个成员(正如我在上面实现的那样),或者你是否需要检查 T 是否恰好存在一次在变体中。如果是后者,那只是一个不同的算法:

template <typename T>
concept VariantMember = mp_count<Var, T>::value == 1;

类似于Yakk,我想出了自己的get。我首先使用 get 的原因是要找出类型 T 是否是 std::variant 的成员。所以我想我只是测试一下我是否可以在 T.

的变体上调用 std::get

无论如何,这是我的解决方案,以防有人觉得它有用:

namespace detail
{
    template <typename Test, typename Variant>
    struct CountVariantMembers;

    template <typename Test, template <typename ...> typename Variant, typename ...Args>
    struct CountVariantMembers<Test, Variant<Args...>>
    {
        static constexpr std::size_t Count = (std::same_as<Test, Args> + ...);
    };
}

template <typename T, typename Variant>
concept VariantMember = details::CountVariantMembers<T, Variant>::Count > 0;