为什么3个版本中有2个版本std::visit不工作?



我试图使用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;
}

https://godbolt.org/z/1c488v

你们的版本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所能做的最好的约束,但这不是一个合适的约束,见下文)。在某些情况下,这是一个无效的类型,因此重载将从考虑中删除。但是如果一个有效类型,那么…这两个重载是完全一样的。它们在两个参数中都是精确匹配的,没有任何东西可以区分它们。

你的第三个版本可以工作,因为否定的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增加了约束的函数模板优于未约束的函数模板的功能。这意味着你可以这样写:

// 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);

意思大致相同

相关内容

  • 没有找到相关文章

最新更新