如何将`boost::static_visitor`实例传递给函数

4

我在我的项目中经常使用boost::variant。我的同事现在提出了一个想法,即传递特定的boost::static_visitor<int>实例以定制访问类型。她有一些类似以下代码的代码:

#include <boost/variant.hpp>
#include <iostream>

typedef boost::variant<int, std::string> TVar;

struct Visitor1 : public boost::static_visitor<int> {
    template<typename T> 
    result_type operator()(const T&) {
        return 42;
    }
};

struct Visitor2 : public boost::static_visitor<int> {
    template<typename T>
    result_type operator()(const T&) {
        return 21;
    }
};

 int adder(const boost::static_visitor<int>& visitor, const TVar& visitable) {
     return visitable.apply_visitor(visitor) + 1;
 }

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(v1, x) << std::endl;
    std::cout << adder(v2, x) << std::endl;
}

我觉得这段代码看起来完全没有问题,但是它无法编译。我的编译器显示某处出现了“表达式将不产生一个接受一个参数的函数”的错误。

我们做错了什么?

3个回答

4
有一个仅接受访问者的 apply_visitor 多载函数。我猜想这会返回一个部分应用的函数对象,符合您的需求:

这个多载函数会返回:

类模板 apply_visitor_delayed_t boost::apply_visitor_delayed_t — 将访问者适配为函数对象。

就我而言,我喜欢在访问者对象中添加一个重载,像这样:

struct MyVisitor {
    typedef void result_type;

    template <typename... Ts>
    result_type operator()(boost::variant<Ts...> const& v) const {
         return boost::apply_visitor(*this, v);
    }
    // optionally repeat for non-const `v`

    // normal variant handling overloads
};

那样的话,您可以直接将访问者对象用作函数对象。

非常有趣。我甚至不知道这个调用存在。但是我仍然不确定如何使用它。假设我有 auto y=boost::apply_visitor(v1)。我该如何将 y 传递给我的 adder 函数呢? - Aleph0
1
你可以像传递可调用对象一样传递它(使用模板类型参数推断参数类型或使用std::function<>等擦除该类型)。 - sehe
非常感谢。我认为Angew已经向我展示了如何做到这一点。 :-) - Aleph0
确实。这也是他方法的一部分。 - sehe

3

static_visitor不是一个多态的基类,因此无法像多态类一样处理。正如其名字所示:它是一个静态(静态类型)的访问者。您需要将访问者的静态类型设置为您想要调用的类型。请注意,这是不可避免的,因为成员函数模板不能是虚拟的。在您的情况下,这意味着adder将不得不成为一个函数模板,由访问者类型进行模板化:

template <class Visitor>
int adder(const Visitor& visitor, const TVar& visitable) {
  return visitable.apply_visitor(visitor) + 1;
}

main中的使用方式将保持不变。

如果您无法将adder变为模板,您可以使用apply_visitor,该函数由@sehe's answer提到,并与std::function(如果您无法使用C++11,则使用boost::function)结合使用:

int adder(std::function<int(const TVar&)> visitorApplier, const TVar& visitable)
{
  return visitorApplier(visitable);
}

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(boost::apply_visitor(v1), x) << std::endl;
    std::cout << adder(boost::apply_visitor(v2), x) << std::endl;
}

【在线例子】

从技术上讲,访问者本身已经是一个合适的可调用对象,所以即使没有显式使用apply_visitor,它也应该可以工作:

int main(int argc, char **args) {
    Visitor1 v1;
    Visitor2 v2;
    TVar x;

    std::cout << adder(v1, x) << std::endl;
    std::cout << adder(v2, x) << std::endl;
}

看起来是一个非常酷的解决方案。不幸的是,它无法编译。我的 Visual Studio 编译器有问题吗?它应该支持 C++11。 - Aleph0
它说无法将boost::apply_visitor_delayed_t<Visitor1>转换为const std::function<int (void)> &。我将传递的变量visitorApplier设置为常量引用。 - Aleph0
刚刚发现,你可以用 adder(v1, x) 替换 adder(boost::apply_visitor(v1), x)。这样你甚至可以缩短你的解决方案。 - Aleph0

3
首先,您应该将访问者的operator()重载标记为const:
struct Visitor1 : public boost::static_visitor<int> {
    template<typename T> 
    result_type operator()(const T&) const {
        return 42;
    }
};

struct Visitor2 : public boost::static_visitor<int> {
    template<typename T>
    result_type operator()(const T&) const {
        return 21;
    }
};

然后,您需要将您的adder更改为接受模板参数而不是boost::static_visitor - 您的Visitor1类型包含您所需的逻辑的operator()重载。您需要“正确的类型”才能进行访问。

template <typename T>
int adder(const T& visitor, const TVar& visitable) {
    return visitable.apply_visitor(visitor) + 1;
}

实时 WandBox 示例


哎呀!太明显了。感谢你的帮助。我真的无法习惯这些模板。 - Aleph0

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