C++ 为什么以下三个版本中的两个std::visit不起作用

C++ 为什么以下三个版本中的两个std::visit不起作用,c++,templates,template-meta-programming,c++20,variant,C++,Templates,Template Meta Programming,C++20,Variant,我试图使用std::visit访问变量中的成员,如果该成员不可用,则抛出一个错误 我能够得到一个有效的解决方案,但我发现前两次尝试中的错误难以理解 有人知道为什么“版本1”和“版本2”不起作用吗 #include <variant> #include <vector> #include <stdexcept> struct a { int value=32; }; struct b : a { }; struct c { //empty

我试图使用
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;
}
#包括
#包括
#包括
结构a
{
int值=32;
};
结构b:a
{
};
结构c
{
//空的
};
使用t=std::variant;
结构古怪的访客{
//版本1(不工作)
//布尔运算符()
// { 
//抛出std::运行时_错误(“坏”);
// };
//版本2(不工作)
//模板
//布尔运算符()(常数L&L,常数R&R)
// { 
//抛出std::运行时_错误(“坏”);
// };
//第三版(作品)
模板

STD:EnabLeFiTyt

你的版本1和版本2的意思完全一样,所以我只考虑版本2。

当您调用
wacky_visitor
时,您有两个重载选择:

//第一次重载
模板
布尔运算符();
//二次过载
模板
??运算符()(常数L&L,常数R&R)
这里的
如果
启用_“约束”(我使用引号是因为它是C++17在约束方面所能做的最好的约束,但它不是一个合适的约束,请参见下文)。在某些情况下,这是一个无效类型,因此将从考虑中删除重载。但如果它是一个有效类型,那么…嗯,我们的两个重载完全相同。这两个重载在两个参数中完全匹配,没有任何区别

您的第三个版本之所以有效,是因为否定的
enable_if
条件确保了两个重载中只有一个是可行的,因此重载解析总是有一个候选项可供选择,而这一个候选项就变得非常好


如果constexpr
具有一个重载,则只需使用
就更容易了:

模板
布尔运算符()(常数L&L,常数R&R)
{ 
if constexpr(std::is_base_of_v&&std::is_base_of_v){
返回l值

在C++20中,Concepts增加了一个功能,即受约束的函数模板比不受约束的模板更受欢迎。这意味着您可以这样编写它:

//第一个重载与前面一样,以语法为准
模板
布尔运算符();
//第二个重载现在受到约束
模板
需要std::is_base_of_v&&std::is_base_of_v
布尔运算符()(常数L&L,常数R&R);
如果第二个重载不可行,则调用第一个重载——与以前一样。但是现在,如果第二个重载可行,则无论如何都可以优先于第一个重载

第二个重载也可以这样写:

模板
布尔运算符()(常数L&L,常数R&R);

这意味着大致相同的事情。

将错误消息复制到此处。@Yakk AdamNevraumont代码现在运行正常,但gcc和clang生成的错误消息(参见godbolt)超出了两个编译器的输出限制:-)另一个有效的技巧是variardics;它们失去了联系。@Berry我一直在从代码生成的角度思考这个问题。想象一下,您有一堆变量类型,可能有200个唯一的变量。这两个变量都是“版本3”发布的答案创建了200 x 200个单独的函数?@Mikhail是的,他们会的。@Barry版本1或版本2呢?