boost::variant
and do something with the data lying there. The standard task for boost::variant
, and the canonical (but very verbose) way to solve it, is to describe the structure inherited from boost::static_visitor
with overloaded operator()
and pass it to boost::apply_visitor
. And this wonderful evening for some reason I became very lazy to write all this bunch of code, and I wanted to have some simpler and more concise way of describing the visitors. What came of it, you can read under the cut. using Variant_t = boost::variant<int, char, std::string, QString, double, float>; template<typename ValType> struct EqualsToValTypeVisitor : boost::static_visitor<bool> { const ValType Value_; EqualsToValTypeVisitor (ValType val) : Value_ { val } { } bool operator() (const std::string& s) const { return Value_ == std::stoi (s); } bool operator() (const QString& s) const { return Value_ == s.toInt (); } template<typename T> bool operator() (T val) const { return Value_ == val; } }; void DoFoo (const Variant_t& var) { const int val = 42; if (boost::apply_visitor (EqualsToValTypeVisitor<int> { val }, var)) // ... }
int
, char
, float
and double
can be described by one template operator, otherwise the operators would be three more, the code would be even more bloated, and it would look still more terrible. void DoFoo (const Variant_t& var) { const int val = 42; const bool isEqual = Visit (var, [&val] (const std::string& s) { return val == std::stoi (s); }, [&val] (const QString& s) { return val == s.toInt (); }, [&val] (auto other) { return other == val; }); }
std::tuple
and sequentially iterating them into the template operator()
its own class, storing them until some time some function with an argument passed to operator()
.Variant_t
, which has among others int
and char
. If it was created with the char
type, and the lambda receiving the int
was first transferred to the visitor creation function, then it will be called first (and successfully!), And the case for char
will not come. Moreover, this problem is really fatal: for the same int
and char
it is impossible (at least, without significant distortions) to determine the order of lambdas so that for both int
and char
passed to the right place, without any type conversions.operator()
. And if we have a structure, then it can be inherited from it, and its operator()
will automatically be in the corresponding scope. And if you inherit from all structures at once, then all their operator()
's will go where necessary, and the compiler will automatically select the operator to call with each specific type, even if the types are given into each other (as in the above-mentioned case int
and char
) . namespace detail { template<typename... Args> struct Visitor : Args... // , variadic pack { Visitor (Args&&... args) : Args { std::forward<Args> (args) }... // { } }; }
boost::variant
and a set of lambdas and visits this very variant
: template<typename Variant, typename... Args> auto Visit (const Variant& v, Args&&... args) { return boost::apply_visitor (detail::Visitor<Args...> { std::forward<Args> (args)... }, v); }
apply_visitor
expects to get a successor to boost::static_visitor
, at least in my version of Boost 1.57 (they say that support for automatic output of the returned type was added later in C ++ 14-mode). template<typename Variant, typename Head, typename... TailArgs> auto Visit (const Variant& v, Head&& head, TailArgs&&... args) { using R_t = decltype (head ({})); //return boost::apply_visitor (detail::Visitor<Head, TailArgs...> { std::forward<Head> (head), std::forward<TailArgs> (args)... }, v); }
std::declval
will not help us here either, because the type taken by the first lambda is not known in advance, and trying to call it with all types in a row from the type list of the variant
is too crutchy and verbose.variant
type list and call our already constructed Visitor
with it. This is guaranteed to work, because the visitor must be able to handle any of the types in the variant
. So: template<typename HeadVar, typename... TailVars, typename... Args> auto Visit (const boost::variant<HeadVar, TailVars...>& v, Args&&... args) -> { using R_t = decltype (detail::Visitor<Args...> { std::forward<Args> (args)... } (std::declval<HeadVar> ())); //return boost::apply_visitor (detail::Visitor<Args...> { std::forward<Args> (args)... }, v); }
Visitor
itself must inherit from boost::static_visitor<R_t>
, and R_t
at this point. Well, it's quite easy to solve by breaking the Visitor
into two classes, one of which deals with inheriting from lambdas and aggregating their operator()
's, and the other implements boost::static_visitor
. namespace detail { template<typename... Args> struct VisitorBase : Args... { VisitorBase (Args&&... args) : Args { std::forward<Args> (args) }... { } }; template<typename R, typename... Args> struct Visitor : boost::static_visitor<R>, VisitorBase<Args...> { using VisitorBase<Args...>::VisitorBase; }; } template<typename HeadVar, typename... TailVars, typename... Args> auto Visit (const boost::variant<HeadVar, TailVars...>& v, Args&&... args) { using R_t = decltype (detail::VisitorBase<Args...> { std::forward<Args> (args)... } (std::declval<HeadVar> ())); return boost::apply_visitor (detail::Visitor<R_t, Args...> { std::forward<Args> (args)... }, v); }
template<typename HeadVar, typename... TailVars, typename... Args> auto Visit (const boost::variant<HeadVar, TailVars...>& v, Args&&... args) -> decltype (detail::VisitorBase<Args...> { std::forward<Args> (args)... } (std::declval<HeadVar> ()))
#define NC nc = std::unique_ptr<int> {} Variant_t v { 'a' }; const auto& asQString = Visit (v, [NC] (const std::string& s) { return QString::fromStdString (s); }, [NC] (const QString& s) { return s; }, [NC] (auto val) { return QString::fromNumber (val); });
template<typename T> void operator() (const std::vector<T>& vec) { //... }
[] (const std::vector& vec) {} . C++17.
[] (const std::vector& vec) {} . C++17.
Source: https://habr.com/ru/post/270689/
All Articles