在 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
的第一个重载。
这些真的是错误还是我搞砸了?
首先,您的 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
重载。
我们如何处理硬错误?写一个没有的get
; safe_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;
我有以下代码:
#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
的第一个重载。
这些真的是错误还是我搞砸了?
首先,您的 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
重载。
我们如何处理硬错误?写一个没有的get
; safe_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.
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;