Как передать экземпляры `boost::static_visitor` в функции
Я использую 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;
}
Это выглядит идеально звучащим для меня, но не компилируется. Мой компилятор говорит, что expression will not yield a function that takes one 1 argument
где-то инсайдер.
Что мы делаем не так?
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;
}
};
Затем вам нужно изменить свой сумматор так, чтобы он принимал параметр шаблона вместо boost::static_visitor
- это твое Visitor1
тип, который содержит operator()
перегружать желаемой логикой. Вам нужен "правильный тип" для посещения.
template <typename T>
int adder(const T& visitor, const TVar& visitable) {
return visitable.apply_visitor(visitor) + 1;
}
Там перегрузка для apply_visitor
это берет только посетителя. Это возвращает частично примененный объект функции, который соответствует вашим потребностям, я полагаю:
Эта перегрузка возвращает
Лично мне нравится добавлять перегрузку к объекту посетителя следующим образом:
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
};
Таким образом, вы можете просто использовать объект посетителя как функцию oject.
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 в сочетании с std::function
(или же boost::function
если вы не можете использовать C++ 11):
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;
}