为什么以下 3 个版本中的 2 个 std::visit 不起作用
Why do the following 2 of 3 versions std::visit not work
我试图使用 std::visit
访问变体中的成员,如果该成员不可用则抛出错误。
我能够找到一个可行的解决方案,但我发现前两次尝试中的错误难以理解。
有人知道为什么“版本 1”和“版本 2”不起作用吗?
#include <variant>
#include <vector>
#include <stdexcept>
struct a
{
int value=32;
};
struct b : a
{
};
struct c
{
//empty
};
using t = std::variant<std::monostate,a,b,c>;
struct wacky_visitor{
// Version 1 (doesn't work)
// bool operator()(const auto& l, const auto& r)
// {
// throw std::runtime_error("bad");
// };
// Version 2 (doesn't work)
// template <typename L, typename R>
// bool operator()(const L& l, const R& r)
// {
// throw std::runtime_error("bad");
// };
// Version 3 (works)
template <typename L, typename R>
std::enable_if_t<!(std::is_base_of_v<a, L> && std::is_base_of_v<a, R>), bool> operator()(const L& l, const R& r)
{
throw std::runtime_error("bad");
};
//Shared
template <typename L, typename R>
std::enable_if_t<std::is_base_of_v<a, L> && std::is_base_of_v<a, R>, bool> operator()(const L& l, const R& r)
{
return l.value < r.value;
};
};
int main()
{
std::vector<t> foo_bar = {a(),b()};
const auto comparison = [](const t &lhs, const t &rhs) {
return std::visit(wacky_visitor{}, lhs, rhs);
};
std::sort(foo_bar.begin(), foo_bar.end(), comparison);
return 0;
}
你的版本 1 和版本 2 的意思完全一样,所以我只考虑版本 2。
当您调用 wacky_visitor
时,您有两个重载选择:
// first overload
template <typename L, typename R>
bool operator()(L const&, R const&);
// second overload
template <typename L, typename R>
???? operator()(const L& l, const R& r)
????
是这个 enable_if
“约束”(我使用引号是因为它是 C++17 就约束而言可以做的最好的,但这不是一个合适的约束,请参阅以下)。在某些情况下,这是无效类型,因此重载将从考虑中移除。但是如果它 是 一个有效的类型,那么……好吧,我们的两个重载是完全一样的。两者在两个参数中都是完全匹配的,没有任何区别。
您的第 3 个版本有效,因为取反的 enable_if
条件确保两个重载中恰好有一个是可行的,因此重载解决方案始终只有一个候选者可供选择——然后它就会成为最好的。
仅使用 if constexpr
并进行一次重载会更容易:
template <typename L, typename R>
bool operator()(const L& l, const R& r)
{
if constexpr (std::is_base_of_v<a, L> && std::is_base_of_v<a, R>) {
return l.value < r.value;
} else {
throw std::runtime_error("bad");
}
};
在 C++20 中,Concepts 增加了 constrained 函数模板优于无约束函数模板的功能。这意味着你可以这样写:
// first overload as before, whichever syntax
template <typename L, typename R>
bool operator()(L const&, R const&);
// second overload is now constrained
template <typename L, typename R>
requires std::is_base_of_v<a, L> && std::is_base_of_v<a, R>
bool operator()(const L& l, const R& r);
如果第二次重载不可行,则像以前一样调用第一个重载。但是现在,如果第二个重载可行,它无论如何都比第一个重载更受欢迎。
第二次重载也可以这样写:
template <std::derived_from<a> L, std::derived_from<a> R>
bool operator()(const L& l, const R& r);
意思差不多。
我试图使用 std::visit
访问变体中的成员,如果该成员不可用则抛出错误。
我能够找到一个可行的解决方案,但我发现前两次尝试中的错误难以理解。
有人知道为什么“版本 1”和“版本 2”不起作用吗?
#include <variant>
#include <vector>
#include <stdexcept>
struct a
{
int value=32;
};
struct b : a
{
};
struct c
{
//empty
};
using t = std::variant<std::monostate,a,b,c>;
struct wacky_visitor{
// Version 1 (doesn't work)
// bool operator()(const auto& l, const auto& r)
// {
// throw std::runtime_error("bad");
// };
// Version 2 (doesn't work)
// template <typename L, typename R>
// bool operator()(const L& l, const R& r)
// {
// throw std::runtime_error("bad");
// };
// Version 3 (works)
template <typename L, typename R>
std::enable_if_t<!(std::is_base_of_v<a, L> && std::is_base_of_v<a, R>), bool> operator()(const L& l, const R& r)
{
throw std::runtime_error("bad");
};
//Shared
template <typename L, typename R>
std::enable_if_t<std::is_base_of_v<a, L> && std::is_base_of_v<a, R>, bool> operator()(const L& l, const R& r)
{
return l.value < r.value;
};
};
int main()
{
std::vector<t> foo_bar = {a(),b()};
const auto comparison = [](const t &lhs, const t &rhs) {
return std::visit(wacky_visitor{}, lhs, rhs);
};
std::sort(foo_bar.begin(), foo_bar.end(), comparison);
return 0;
}
你的版本 1 和版本 2 的意思完全一样,所以我只考虑版本 2。
当您调用 wacky_visitor
时,您有两个重载选择:
// first overload
template <typename L, typename R>
bool operator()(L const&, R const&);
// second overload
template <typename L, typename R>
???? operator()(const L& l, const R& r)
????
是这个 enable_if
“约束”(我使用引号是因为它是 C++17 就约束而言可以做的最好的,但这不是一个合适的约束,请参阅以下)。在某些情况下,这是无效类型,因此重载将从考虑中移除。但是如果它 是 一个有效的类型,那么……好吧,我们的两个重载是完全一样的。两者在两个参数中都是完全匹配的,没有任何区别。
您的第 3 个版本有效,因为取反的 enable_if
条件确保两个重载中恰好有一个是可行的,因此重载解决方案始终只有一个候选者可供选择——然后它就会成为最好的。
仅使用 if constexpr
并进行一次重载会更容易:
template <typename L, typename R>
bool operator()(const L& l, const R& r)
{
if constexpr (std::is_base_of_v<a, L> && std::is_base_of_v<a, R>) {
return l.value < r.value;
} else {
throw std::runtime_error("bad");
}
};
在 C++20 中,Concepts 增加了 constrained 函数模板优于无约束函数模板的功能。这意味着你可以这样写:
// first overload as before, whichever syntax
template <typename L, typename R>
bool operator()(L const&, R const&);
// second overload is now constrained
template <typename L, typename R>
requires std::is_base_of_v<a, L> && std::is_base_of_v<a, R>
bool operator()(const L& l, const R& r);
如果第二次重载不可行,则像以前一样调用第一个重载。但是现在,如果第二个重载可行,它无论如何都比第一个重载更受欢迎。
第二次重载也可以这样写:
template <std::derived_from<a> L, std::derived_from<a> R>
bool operator()(const L& l, const R& r);
意思差不多。