当函数对象需要多个参数时,正确使用std::variant和std::visit。

3

我一直在努力让我的代码使用std::variantstd::visit组合起来工作。现在,我已经将我的代码简化到只使用一个类型的variant,但编译器仍然抱怨没有匹配的调用visit。

#include <iostream>
#include <variant>
#include <vector>
#include <array>
class SomeClass {
    
    public:
    
    std::vector<int> foo(const std::string& s, const std::array<float, 3>& d){
        return {};
    }
};

struct Visitor{
    public:
    
    std::vector<int> operator() (SomeClass& c1, const std::string& s, const std::array<float, 3>& a) {
        
        return c1.foo(s, a);        
        
    }
};

int main() {
    
    std::variant<SomeClass> variants;
    
    std::string s = "c++";
    std::array<float, 3> a {1, 0, 0};
    
    std::visit(Visitor{}, variants, s, a);

    return 0;
}

我正在使用gcc 11.3

2个回答

4

std::visit 期望访问者(即函数对象)后的每个参数都是一个 variant。它会将每个 variant 展开到基础对象,然后使用这些对象调用访问者。如果您尝试提供非 variant 类型(例如 string),则会出现错误。

访问者可以具有自己的内部状态/变量。您想将 sa 带入您的访问者中。也就是说,sa 不是访问者的概念性参数。您应该将它们视为访问者的一部分,因为访问者对象代表“对 variant 执行什么操作”,而 sa 是该操作的一部分。sa 不是访问者的输入,因为访问者的输入应该是 variant

struct Visitor {
    std::string s;
    std::array<float, 3> a;
    std::vector<int> operator()(SomeClass &c) {
        return c.foo(s, a);
    }
};

std::variant<SomeClass> variants;
Visitor v{"c++", {1, 0, 0}};
std::visit(v, variants);
// you *could* shorten this to a lambda with captures
// std::string s = "c++"
// std::array<float, 3> a{1, 0, 0};
// std::visit([&](SomeClass &c) { return c.foo(s, a); }, variants);
// but this will not generalize to multiple things in the variant (lambdas don't overload)

还有以下这种hack。我认为这种做法风格不好(因为它不能正确地将访问者与被访问的内容分离),但是如果你必须使用它,它确实有效。

template<typename T>
std::variant<std::reference_wrapper<T>> unvisit(T &x) {
    return std::ref(x);
}
// using your original Visitor this time
std::variant<SomeClass> variants;
std::string s = "c++";
std::array<float, 3> a {1, 0, 0};

std::visit(Visitor{}, variants, unvisit(s), unvisit(a));

即,您可以通过引用将每个“非variant”参数包装到单变体variant中,然后依靠visit再次解包它。


谢谢。我最终使用了第一个建议。不过,我发现可以使用template<class... Ts> struct overload : Ts... { using Ts::operator()...; };来进行lambda函数的重载。 然后: std::visit(overload { /*第一个lambda*/, /*第二个lambda*/}, variants); - ATK

1

std::visit 会通过将被访问对象作为参数,始终只传递一个参数,来调用访问者。

std::visit 的附加参数是要访问的其他变量

std::visit(Visitor{}, variants, s, a);

这意味着:使用visitor{}访问variants,然后访问s,最后访问a。所有这些都应该是变量。
换句话说:std::visit的工作方式并非您所想象的那样。您需要想出其他替代手段来访问您的变体。

哦,我没有想到那会是一个限制。唉。 - ATK
std::visit(visitor, variant1, ... variantn) 通过传递 n 个参数来调用访问者。即 std::visit 总是通过传递与其拥有的访问者数量相同的参数来调用访问者。特别地,单次调用 std::visit 只会调用一次 visitor,而不是多次连续调用。 - HTNW
我应该如何解决这个问题?我需要传递两个随时变化的变量,每次我需要访问时都会不同。现在对象已经被包装在变体中,因此我想避免使用get < >来将这些变量“设置”为类的某些内部状态。 - ATK
通过std::visit逐个访问每个变量,访问捕获参数的lambda函数,并调用真正的访问函数,将参数转发给它? - Sam Varshavchik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接