What Are the Differences Between Std::Variant and Boost::Variant

What are the differences between std::variant and boost::variant?

  • Assignment/emplacement behavior:

    • boost::variant may allocate memory when performing assignment into a live variant. There are a number of rules that govern when this can happen, so whether a boost::variant will allocate memory depends on the Ts it is instantiated with.

    • std::variant will never dynamically allocate memory. However, as a concession to the complex rules of C++ objects, if an assignment/emplacement throws, then the variant may enter the "valueless_by_exception" state. In this state, the variant cannot be visited, nor will any of the other functions for accessing a specific member work.

      You can only enter this state if assignment/emplacement throws.

  • Boost.Variant includes recursive_variant, which allows a variant to contain itself. They're essentially special wrappers around a pointer to a boost::variant, but they are tied into the visitation machinery.

    std::variant has no such helper type.

  • std::variant offers more use of post-C++11 features. For example:

    • It forwards the noexcept status of the special member functions of its constituent types.

    • It has variadic template-based in-place constructors and emplacement functions.

    • Defect resolutions applied to C++17 may mean that it will also forward trivial copyability of its types. That is, if all of the types are trivially copyable, then so too will variant<Ts>.

Difference between std::optional and boost::optional when its value is a variant

boost::optional doesn't have a constructor that takes arguments to pass to the underlying type but std::optional does (see constructor number 8)

You need to explicitly call the optional constructor:

boost::optional<boost::variant<int, std::string>> f()
{
return boost::optional<boost::variant<int, std::string>>{5};
}

Or as the construction from the contained value type is non-explicit:

boost::optional<boost::variant<int, std::string>> f()
{
return boost::variant<int, std::string>{5};
}

How to properly replace boost::variant by std::variant?

As you have duplicate types in your variant some of the constructors are disabled:

This overload only participates in overload resolution if there is exactly one occurrence of T in Types...

You need to use the constructor with an explicit type index:

return std::variant<T...>(std::in_place_index<n>, std::get<n>(tpl));

Your original boost code has undefined behaviour:

Each type specified as a template argument to variant must be distinct after removal of qualifiers. Thus, for instance, both variant<int, int> and variant<int, const int> have undefined behavior.

The standard libary implementation does support duplicate types and therefore prevents you from accidentally constructing an ambigous variant. For example what should the following do:

variant<std::string, double, double, int> t = 4.5;

With boost it is UB, either of the double values might be initialised or it might do something completely different. The standard library explitly makes this a compiler error so that you have to choose which of your doubles you want to initialise.

Differences between `boost::any` and `std::any`

I'm interested to the differences between the behavior and the guarantees.

There aren't any behavioral differences; not really. They both have the same requirements on the ValueType (copy-constructible, and a destructor that doesn't emit exceptions). They both provide the same operations on the values they store, with pretty much identical exception guarantees.

The principle difference is that boost::any's implementation at present doesn't implement small object optimization, while std::any implementations may provide it.

Boost.Any vs. Boost.Variant

Have you looked at the comparison in the variant library already?

(Not sure what states from external sources are, so it's kind of hard to say what's more appropriate for you.)

Transitioning Boost Spirit parser from boost::variant to std::variant

Somehow boost::variant avoids the error.

Yeah. Boost variant has attribute propagation
support.

Besides, boost::variant has special handling of boost::recursive_wrapper so it might be a double no-fly.

A good article about recursive std::variants is here https://vittorioromeo.info/index/blog/variants_lambdas_part_2.html

What's wrong with boost::variant?

If you want you can write some transformation traits, or even look into x3::variant - it might suit you better?

Live On Coliru

#include <string>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

struct Recurse;
using Base = x3::variant<
std::string,
x3::forward_ast<Recurse> >;

struct Recurse
{
int _i;
Base _base;
};

BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)

const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;

BOOST_SPIRIT_DEFINE(base)

int main()
{
std::string text;
Base result;
x3::phrase_parse(std::begin(text), std::end(text), base, ascii::space, result);
return 0;
}

Side note: No x3::forward_ast<> does not help with std::variant, confirming that std::variant just lacks support in x3

UPDATE

You can work-around things by making your Base a derived struct with the required machinery to indicate to Spirit that it is a variant (and over which types). That way you don't have to go through trait specialization hell:

struct Recurse;

struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;

struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};

struct Recurse {
int _i;
Base _base;
};

As you can see, it's basically the same¹, but adds adapted_variant_tag and types nested types.

Note that by cleverly hardcoding the types sequence, we can pretend to handle the recursive wrapper smartly. We're lucky that this is enough to fool the system.

Adding some debug output and test-cases:

Live On Coliru

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}

struct Recurse;

struct Base : std::variant<std::string, boost::recursive_wrapper<Recurse> > {
using BaseV = std::variant<std::string, boost::recursive_wrapper<Recurse> >;
using BaseV::BaseV;
using BaseV::operator=;

struct adapted_variant_tag {};
using types = boost::mpl::list<std::string, Recurse>;
};

struct Recurse {
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};

BOOST_FUSION_ADAPT_STRUCT(
Recurse,
(int, _i),
(Base, _base)
)

static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto operand = *x3::char_("a-zA-Z0-9_") | base;
const auto base_def = (x3::int_ >> operand) | operand;

BOOST_SPIRIT_DEFINE(base)

int main()
{
for (std::string const text : { "yeah8", "32 more" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, base, ascii::space, result)) {
std::cout << "Result: " << result << "\n";
} else {
std::cout << "Failed\n";
}

if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
}

}
}

Which prints

Result: yeah8
Result: [32, more]

Update 2: Icing The Cake

Here's the traits required to make std::variant just work:

namespace boost::spirit::x3::traits {
template<typename... t>
struct is_variant<std::variant<t...> >
: mpl::true_ {};

template <typename attribute, typename... t>
struct variant_has_substitute_impl<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;

typedef typename mpl::find<types, attribute>::type iter_1;

typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute>>,
mpl::identity<iter_1>
>::type
iter;

typedef mpl::not_<is_same<iter, end>> type;
};

template <typename attribute, typename... t>
struct variant_find_substitute<std::variant<t...>, attribute>
{
typedef std::variant<t...> variant_type;
typedef typename mpl::transform<
mpl::list<t...>
, unwrap_recursive<mpl::_1>
>::type types;

typedef typename mpl::end<types>::type end;

typedef typename mpl::find<types, attribute>::type iter_1;

typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, attribute> >,
mpl::identity<iter_1>
>::type
iter;

typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<attribute>,
mpl::deref<iter>
>::type
type;
};

template <typename... t>
struct variant_find_substitute<std::variant<t...>, std::variant<t...> >
: mpl::identity<std::variant<t...> > {};
}

That's a lot of noise but you can put it away in a header somewhere.

BONUS

Fixing the grammar:

  • you probably meant to have lexeme[] around the string production
  • you probably meant to have a minimal lenght of the string (+char_, not *char_) seeing that there are no delimiters
  • you may have have to reorder the branches because the string production would gobble up integers for recursed rules.

Here's my touched-up take on the grammar, where the rules closely mirror the AST, as usually makes sense:

namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}

Simplify Fusion

Last but not least, in C++11 era you can deduce the adapted fusion members:

BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)

Live Full Demo

Live On Coliru

#include <string>
#include <variant>
#include <iostream>
#include <iomanip>
#include <boost/spirit/home/x3.hpp>
#include <boost/spirit/home/x3/support/ast/variant.hpp>
#include <boost/variant/recursive_wrapper.hpp>
#include <boost/fusion/include/adapt_struct.hpp>

namespace x3 = boost::spirit::x3;
namespace ascii = boost::spirit::x3::ascii;

namespace { // for debug
template<class T>
std::ostream& operator<<(std::ostream& os, boost::recursive_wrapper<T> const& rw) {
return os << rw.get();
}
template<class... Ts>
std::ostream& operator<<(std::ostream& os, std::variant<Ts...> const& sv) {
std::visit([&os](const auto& v) { os << v; }, sv);
return os;
}
}

struct Recurse;
using Base = std::variant<
std::string,
boost::recursive_wrapper<Recurse> >;

namespace boost::spirit::x3::traits {
template<typename... T>
struct is_variant<std::variant<T...> >
: mpl::true_ {};

template <typename Attribute, typename... T>
struct variant_has_substitute_impl<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;
typedef typename mpl::end<types>::type end;

typedef typename mpl::find<types, Attribute>::type iter_1;

typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute>>,
mpl::identity<iter_1>
>::type
iter;

typedef mpl::not_<is_same<iter, end>> type;
};

template <typename Attribute, typename... T>
struct variant_find_substitute<std::variant<T...>, Attribute>
{
typedef std::variant<T...> variant_type;
typedef typename mpl::transform<
mpl::list<T...>
, unwrap_recursive<mpl::_1>
>::type types;

typedef typename mpl::end<types>::type end;

typedef typename mpl::find<types, Attribute>::type iter_1;

typedef typename
mpl::eval_if<
is_same<iter_1, end>,
mpl::find_if<types, traits::is_substitute<mpl::_1, Attribute> >,
mpl::identity<iter_1>
>::type
iter;

typedef typename
mpl::eval_if<
is_same<iter, end>,
mpl::identity<Attribute>,
mpl::deref<iter>
>::type
type;
};

template <typename... T>
struct variant_find_substitute<std::variant<T...>, std::variant<T...> >
: mpl::identity<std::variant<T...> > {};
}

static_assert(x3::traits::is_variant<Base>{}, "");

struct Recurse
{
int _i;
Base _base;
friend std::ostream& operator<<(std::ostream& os, Recurse const& r) {
return os << "[" << r._i << ", " << r._base << "]";
}
};

BOOST_FUSION_ADAPT_STRUCT(Recurse, _i, _base)

namespace Parser {
static_assert(x3::traits::is_variant<Base>::value);
const x3::rule<class Base_, Base> base = "base";
const auto string = x3::lexeme[+x3::char_("a-zA-Z0-9_")];
const auto recurse = x3::int_ >> base;
const auto base_def = recurse | string;
BOOST_SPIRIT_DEFINE(base)
}

int main()
{
for (std::string const text : { "yeah8", "32 more", "18 766 most" }) {
Base result;
auto f = begin(text), l = end(text);
if (x3::phrase_parse(f, l, Parser::base, ascii::space, result)) {
std::cout << "Result: " << result << "\n";
} else {
std::cout << "Failed\n";
}

if (f!=l) {
std::cout << "Remaining input: " << std::quoted(std::string(f,l)) << "\n";
}
}
}

Which prints:

Result: yeah8
Result: [32, more]
Result: [18, [766, most]]

¹ (the subtle difference MAY bite you in generic programming where you need to access the base-class explicitly)

Error while trying to use the boost::variant - No matching function for call

Here's my imagined self-contained tester:

Live On Coliru

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

struct X;
struct Y;
template <typename... T> struct ClassA {
void operate(double d, int a, float b) const
{
std::cout << __PRETTY_FUNCTION__ << "(" << d << "," << a << "," << b << ")\n";
}
};

struct Output {
int a;
float b;
};

typedef boost::variant<ClassA<X, Y>, ClassA<>> ClassAGeneric;

class Operation // : public boost::static_visitor<Output>
{
public:
double d;
int a;
float b;

Output operator()(ClassA<X, Y> const& obj) const
{
obj.operate(d, a, b);
return Output{a, b};
}

Output operator()(ClassA<> const& obj) const
{
obj.operate(d, a, b);
return Output{a, b};
}
};

int main() {
Operation op {3.14, 42, 9e-2f};

ClassAGeneric v1 = ClassA<X,Y>{};
ClassAGeneric v2 = ClassA<>{};
apply_visitor(op, v1);
apply_visitor(op, v2);
}

Prints

void ClassA::operate(double, int, float) const with T = {X, Y}

void ClassA::operate(double, int, float) const with T = {}

Unsurprisingly that works. Now, one pitfall could be when you failed to make the operate member function const and the arguments are, in fact, const.

Also note that you can greatly simplify the visitor (especially assuming C++14): Live On Coliru

std::variant and ambiguous initialization

Before P0608, variant<int, long double> v{42.3} also has the ambiguous issue since 42.3 can be converted to int or long double.

P0608 changed the behavior of variant's constructors:

template<class T> constexpr variant(T&& t) noexcept(see below);

  • Let Tj be a type that is determined as follows: build an imaginary function FUN(Ti) for each alternative type Ti for which Ti x[] = {std::forward<T>(t)}; is well-formed for some invented variable x and,
    if Ti is cv bool, remove_cvref_t<T> is bool.
    The overload FUN(Ti)
    selected by overload resolution for the expression
    FUN(std::forward<T>(t)) defines the alternative Tj which is the type
    of the contained value after construction.

In your example, the variant has two alternative types: int and long double, so we can build the following expression

        int x[] = {std::forward<double>(42.3)}; // #1
long double y[] = {std::forward<double>(42.3)}; // #2

Since only #2 is well-formed, the variant successfully deduces the type of the contained value type long double.



Related Topics



Leave a reply



Submit