Boost C++ 库

one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

Boost.Variant2:永不“无值”的 variant 类型 - Boost C++ 函数库

概述

描述

此库实现了一个类型安全的、带标签的联合(discriminated/tagged union)类型 variant<T…​>,其 API 与 C++17 标准中的 std::variant<T…​> 兼容。

variant<T1, T2, …​, Tn> 变量可以存储 T1T2、…​、Tn 中任何一种类型的值。例如,variant<int64_t, double, std::string> 可以存储一个 int64_t 值、一个 double 值或一个 string 值。

这种类型有时被称为“带标签的联合”,因为它大致等同于

struct V
{
    enum tag { tag_int64_t, tag_double, tag_string };

    tag tag_;

    union
    {
        int64_t     i_;
        double      d_;
        std::string s_;
    };
};

使用示例

Variant 可用于表示动态类型的值。一种形式为

server.host=test.example.com
server.port=9174
cache.max_load=0.7

的配置文件可以表示为 std::map<std::string, variant<int64_t, double, std::string>>

Variant 也可以表示多态。以经典的形状为例,一个多态形状集合

#define _USE_MATH_DEFINES
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>

class Shape
{
public:

    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Rectangle: public Shape
{
private:

    double width_, height_;

public:

    Rectangle( double width, double height ):
        width_( width ), height_( height ) {}

    virtual double area() const { return width_ * height_; }
};

class Circle: public Shape
{
private:

    double radius_;

public:

    explicit Circle( double radius ): radius_( radius ) {}
    virtual double area() const { return M_PI * radius_ * radius_; }
};

double total_area( std::vector<std::unique_ptr<Shape>> const & v )
{
    double s = 0.0;

    for( auto const& p: v )
    {
        s += p->area();
    }

    return s;
}

int main()
{
    std::vector<std::unique_ptr<Shape>> v;

    v.push_back( std::unique_ptr<Shape>( new Circle( 1.0 ) ) );
    v.push_back( std::unique_ptr<Shape>( new Rectangle( 2.0, 3.0 ) ) );

    std::cout << "Total area: " << total_area( v ) << std::endl;
}

可以改用 variant<Rectangle, Circle> 值集合来表示。这要求已预先知道所有可能的 Shape 类型,这通常是情况。作为回报,我们不再需要虚函数,也不再需要用 new Rectanglenew Circle 将值分配到堆上。

#define _USE_MATH_DEFINES
#include <iostream>
#include <vector>
#include <cmath>

#include <boost/variant2/variant.hpp>
using namespace boost::variant2;

struct Rectangle
{
    double width_, height_;
    double area() const { return width_ * height_; }
};

struct Circle
{
    double radius_;
    double area() const { return M_PI * radius_ * radius_; }
};

double total_area( std::vector<variant<Rectangle, Circle>> const & v )
{
    double s = 0.0;

    for( auto const& x: v )
    {
        s += visit( []( auto const& y ){ return y.area(); }, x );
    }

    return s;
}

int main()
{
    std::vector<variant<Rectangle, Circle>> v;

    v.push_back( Circle{ 1.0 } );
    v.push_back( Rectangle{ 2.0, 3.0 } );

    std::cout << "Total area: " << total_area( v ) << std::endl;
}

构造与赋值

如果我们查看

    v.push_back( Circle{ 1.0 } );

行,我们可以推断出 variant<Rectangle, Circle> 可以(隐式)从 Circle(和 Rectangle)构造,并且确实可以。它也可以被赋值一个 CircleRectangle

variant<Rectangle, Circle> v = Circle{ 1.0 }; // v holds Circle
v = Rectangle{ 2.0, 3.0 };                    // v now holds Rectangle

如果我们尝试用不是 int 也不是 float 的内容来构造 variant<int, float>,例如 (short)1,行为将“如同” variant 声明了两个构造函数,

variant::variant(int x);
variant::variant(float x);

并且将使用标准的重载解析规则来选择将使用的那个。所以 variant<int, float>((short)1) 将会存储一个 int

检查值

将值放入 variant 很容易,但将其取出必然会有些曲折。variant<int, float> 无法定义一个成员函数 get() const,因为这样的函数需要在其返回类型在编译时确定,而正确的返回类型是 int 还是 float 只有在运行时才知道。

对此有几种方法。首先,有一个访问器成员函数

std::size_t variant::index() const noexcept;

它返回当前类型的零基索引。对于 variant<int, float>,它将返回 0 表示 int,返回 1 表示 float

一旦有了索引,我们就可以使用自由函数 get<N> 来获取值。由于我们将类型索引传递给 get,所以它知道返回什么。get<0>(v) 将返回 intget<1>(v) 将返回 float

void f( variant<int, float> const& v )
{
    switch( v.index() )
    {
    case 0:

        // use get<0>(v)
        break;

    case 1:

        // use get<1>(v)
        break;

    default:

        assert(false); // never happens
    }
}

如果我们调用 get<0>(v),而 v.index() 当前不是 0,则将抛出异常(类型为 bad_variant_access)。

另一种方法是使用 get<int>(v)get<float>(v)。这工作方式类似。

另一种避免 bad_variant_access 异常的方法是使用 get_if。它不返回对包含值的引用,而是返回指向该值的指针,返回 nullptr 表示类型不匹配。get_if 接受一个指向 variant 的指针,所以在这个例子中,我们将使用类似以下的行

void f( variant<int, float> const& v )
{
    if( int const * p = get_if<int>(&v) )
    {
        // use *p
    }
    else if( float const * p = get_if<float>(&v) )
    {
        // use *p
    }
    else
    {
        assert(false); // never happens
    }
}

访问

最后但同样重要的是 visitvisit(f, v)variant v 中包含的值调用函数对象 f 并返回结果。当 vvariant<int, float> 时,它将用 intfloat 调用 f。函数对象必须准备好接受两者。

实际上,这可以通过让函数接受可以传递 intfloat 的类型来实现,例如 double

double f( double x ) { return x; }

double g( variant<int, float> const& v )
{
    return visit( f, v );
}

通过使用带有重载的 operator() 的函数对象

struct F
{
    void operator()(int x) const { /* use x */ }
    void operator()(float x) const { /* use x */ }
};

void g( variant<int, float> const& v )
{
    visit( F(), v );
}

或者使用多态 lambda,就像我们在 Circle/Rectangle 示例中所做的那样

void g( variant<int, float> const& v )
{
    visit( [&]( auto const& x ){ std::cout << x << std::endl; }, v );
}

visit 也可以接受多个 variantvisit(f, v1, v2) 调用 f(x1, x2),其中 x1v1 中包含的值,x2v2 中包含的值。

默认构造

variant 的默认构造函数将对列表中的第一个类型进行值初始化。variant<int, float>{} 存储 0(类型为 int),而 variant<float, int>{} 存储 0.0f

这通常是期望的行为。但是,在 variant<std::mutex, std::recursive_mutex> 等情况下,人们可能希望避免默认构造 std::mutex。提供的一种类型 monostate 可以在这些场景中用作第一个类型。variant<monostate, std::mutex, std::recursive_mutex> 将默认构造一个 monostate,它基本上是一个无操作,因为 monostate 实际上是一个空的 struct

修订历史

1.88.0 版中的更改

  • 使用最小的合适无符号类型来表示索引。

1.83.0 版的更改

  • 添加了 uses_double_storage()

1.81.0 版本中的更改

  • 添加了对 boost::json::value_fromboost::json::value_to 的支持。

1.79.0 版中的更改

  • 添加了 operator<< 用于 monostate

1.78.0 版的更改

  • 添加了 <boost/variant2.hpp>

  • 添加了 unsafe_get<I>

  • 添加了 visit_by_index

  • 添加了 operator<<

1.76.0 版本中的更改

  • 改进了双缓冲情况下的生成代码。

1.74.0 版中的更改

  • 添加了对 visit 中派生类型的支持。

  • 提高了拥有大量(数百个)备选项时的编译性能。

  • 添加了对 visit<R> 的支持。

1.73.0 版的更改

  • 添加了对 std::hashboost::hash 的支持。

  • variant<T…​> 现在是平凡的(trivial),当 T…​ 中的所有类型都是平凡的。这通过使其能够通过寄存器传递和返回来提高性能。

1.71.0 版中的更改

经过 Boost 形式评审后,实现已更改为提供强异常安全保证,而不是基本保证。已移除 expected

设计

功能

variant 实现有两个显著特点:

  • 它永不“无值”,即 variant<T1, T2, …​, Tn> 始终包含 T1T2、…​、Tn 中某个类型的有效值。

  • 它在赋值和 emplace 时提供强异常安全保证。

这是通过使用双存储实现的,除非所有包含的类型都具有非抛出移动构造函数。

基本原理

永不“无值”

从直观上看,variant<X, Y, Z> 只能存储 XYZ 类型的值,除此之外不能存储其他任何值。

如果我们把 variant 看作是 union 的扩展,因为 union 有一个名为“无活动成员”的状态,所以可以说 variant<X, Y, Z> 也应该有一个额外的状态,不包含 XYZ 中的任何一个。

然而,这使得 variant 在实践中不太方便,作为构建块的用途也更少。如果我们确实需要一个只存储 XYZ 的变量,额外的空状态会带来需要解决的复杂性。并且在需要这个额外的空状态的情况下,我们可以直接使用 variant<empty, X, Y, Z>,其中 empty 是一个合适的 struct empty {};

从纯粹的设计角度来看,没有额外空状态的论证是坚实的。然而,实现上的考虑则相反。

当我们用另一个值(类型为 Y)替换 variant 的当前值(例如类型为 X)时,因为新值需要占用与旧值相同的存储空间,所以我们必须先销毁旧的 X,然后在其位置构造一个新的 Y。但由于这是 C++,构造可能会抛出异常。此时 variant 处于我们已经同意它不能处于的“无活动成员”状态。

这是一个合法的问题,正是这个问题使得拥有空/无值状态如此有吸引力。我们只需在异常时将 variant 留空即可。

如上所述,从设计角度来看,这是不可取的,因为它降低了组件的实用性和优雅性。

有几种方法可以解决这个问题。最直接的方法是禁止构造可能抛出异常的类型。因为我们总是可以先创建一个临时值,然后使用移动构造函数来初始化 variant 中的值,所以要求非抛出移动构造函数就足够了,而不是要求所有构造函数都非抛出。

不幸的是,在至少一种流行的标准库实现中,基于节点的容器(如 std::liststd::map)具有可能抛出异常的移动构造函数。禁止 variant<X, std::map<Y, Z>> 几乎是不切实际的,所以无法避免异常情况。

在异常时,我们也可以构造另一个值,使 variant 保持有效;但在一般情况下,该构造也可能抛出异常。如果其中一种类型具有非抛出默认构造函数,我们可以使用它;如果没有,我们就无法使用。

Boost.Variant 在此处采用的方法是将值的临时副本存储在堆上。在异常时,指向该临时副本的指针可以存储在 variant 中。指针操作不会抛出异常。

另一种选择是使用双缓冲。如果我们的 variant 占用双倍存储空间,我们可以在未使用的部分构造新值,然后在构造成功后,销毁另一半的旧值。

std::variant 被标准化时,上述方法中没有一种被认为是可接受的,因为它们要么引入了开销,要么对 variant 可以包含的类型过于限制。因此,作为一种妥协,std::variant 采用了这样一种方式,可以(不客气地)被描述为“鱼与熊掌兼得”。

由于描述的异常情况相对罕见,std::variant 有一个特殊情况,称为“无值”(valueless),它会在异常时进入,但其接口尽可能少地承认其存在,允许用户假装它不存在。

从实际角度来看,这也许不算太糟糕,但许多人仍有顾虑。罕见的状态“永不”发生意味着测试不足,而当“永不”真的发生时,通常是最不方便的时候。

此实现不遵循 std::variant;它静态保证 variant 永远不会处于无值状态。为兼容性提供了 valueless_by_exception 函数,但它总是返回 false

相反,如果包含的类型使得在更改包含值时无法避免异常情况,则会使用双存储。

强异常安全

最初提交的版本仅提供了基本异常安全保证。如果尝试更改包含的值(通过赋值或 emplace)时发生异常,并且备选项中存在具有非抛出默认构造函数的类型,则会将该类型的值创建到 variant 中。此决定的优点是双存储的使用频率较低。

评审员们普遍不喜欢这种做法。构造一个随机类型被认为过于不可预测,并且不符合基本保证的精神。即使是非抛出的,所选类型的默认构造函数也可能产生不良的副作用。或者,如果不是这样,该类型的值可能对周围的代码具有特殊意义。因此,有人认为 variant 应该保留其旧值,或者过渡到新值,而不合成其他状态。

另一方面,有些人认为双存储是不可接受的。但他们认为原则上是不可接受的,无论其使用频率如何。

因此,在赋值和 emplace 上提供强异常安全保证被宣布为接受条件。

回想起来,这是一个正确的决定。通常不提供强保证的原因是它无法组合。当 XY 在赋值时提供基本保证时,struct { X x; Y y; }; 也提供基本保证。同样,当 XY 具有非抛出赋值时,该 struct 也具有非抛出赋值。但这不适用于强保证。

通常的做法是在赋值时提供基本保证,让用户通过非抛出 swap 或非抛出移动赋值来合成“强”赋值。也就是说,给定类型为 Xx1x2,而不是“基本”的 x1 = x2;,使用 X(x2).swap(x1);x1 = X(x2);

几乎所有类型都提供非抛出 swap 或非抛出移动赋值,因此这工作得很好。几乎所有,除了 variant,在一般情况下,它既没有非抛出 swap,也没有非抛出移动赋值。如果 variant 本身不提供强保证,用户就无法合成它。

所以它应该如此,并且它也确实如此。

与 std::variant 的区别

此实现与 std::variant 的主要区别在于:

  • 无“异常无值”状态:valueless_by_exception() 始终返回 false

  • 在赋值和 emplace 上提供强异常安全保证。

  • emplace 先构造新值,然后销毁旧值;在单存储情况下,这相当于构造一个临时对象,然后将其移动到位。

  • 提供了一个从例如 variant<int, float>variant<float, double, int> 的转换构造函数作为扩展。

  • 反向操作,从 variant<float, double, int>variant<int, float>,作为成员函数 subset<U…​> 提供。(如果当前 variant 状态无法表示,此操作可能会抛出异常。)

  • 提供 unsafe_get,作为 getget_if 的未经检查的替代方法,作为扩展。

  • 提供 visit_by_index,一个接受单个 variant 和多个函数对象的访问函数,每个备选项一个,作为扩展。

  • 尚未实现 C++20 对 std::variant 的添加和更改。

与 Boost.Variant 的区别

此库与 std::variant 的 API 兼容。因此,其接口与 Boost.Variant 不同。例如,访问是通过 visit 而不是 apply_visitor 执行的。

不支持递归 variant。

使用双存储而不是临时堆备份。此 variant 始终是“基于栈的”,它从不分配,并且其自身从不抛出 bad_alloc

实现

依赖项

此实现仅依赖于 Boost.Config、Boost.Assert 和 Boost.Mp11。

支持的编译器

  • GCC 4.8 或更高版本,使用 -std=c++11 或更高版本

  • Clang 3.9 或更高版本,使用 -std=c++11 或更高版本

  • Visual Studio 2015 或更高版本

Github ActionsAppveyor 上进行了测试。

参考

<boost/variant2/variant.hpp>

提要

namespace boost {
namespace variant2 {

// in_place_type

template<class T> struct in_place_type_t {};
template<class T> constexpr in_place_type_t<T> in_place_type{};

// in_place_index

template<std::size_t I> struct in_place_index_t {};
template<std::size_t I> constexpr in_place_index_t<I> in_place_index{};

// variant

template<class... T> class variant;

// variant_size

template<class T> struct variant_size {};

template<class T> struct variant_size<T const>: variant_size<T> {};
template<class T> struct variant_size<T volatile>: variant_size<T> {};
template<class T> struct variant_size<T const volatile>: variant_size<T> {};

template<class T> struct variant_size<T&>: variant_size<T> {}; // extension
template<class T> struct variant_size<T&&>: variant_size<T> {}; // extension

template<class T>
  inline constexpr size_t variant_size_v = variant_size<T>::value;

template<class... T>
  struct variant_size<variant<T...>>:
    std::integral_constant<std::size_t, sizeof...(T)> {};

// variant_alternative

template<size_t I, class T> struct variant_alternative {};

template<size_t I, class T> struct variant_alternative<I, T const>;
template<size_t I, class T> struct variant_alternative<I, T volatile>;
template<size_t I, class T> struct variant_alternative<I, T const volatile>;

template<size_t I, class T> struct variant_alternative<I, T&>; // extension
template<size_t I, class T> struct variant_alternative<I, T&&>; // extension

template<size_t I, class T>
  using variant_alternative_t = typename variant_alternative<I, T>::type;

template<size_t I, class... T>
  struct variant_alternative<I, variant<T...>>;

// variant_npos

constexpr std::size_t variant_npos = -1;

// holds_alternative

template<class U, class... T>
  constexpr bool holds_alternative(const variant<T...>& v) noexcept;

// get

template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&
    get(variant<T...>& v);
template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&&
    get(variant<T...>&& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&
    get(const variant<T...>& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&&
    get(const variant<T...>&& v);

template<class U, class... T>
  constexpr U& get(variant<T...>& v);
template<class U, class... T>
  constexpr U&& get(variant<T...>&& v);
template<class U, class... T>
  constexpr const U& get(const variant<T...>& v);
template<class U, class... T>
  constexpr const U&& get(const variant<T...>&& v);

// get_if

template<size_t I, class... T>
  constexpr add_pointer_t<variant_alternative_t<I, variant<T...>>>
    get_if(variant<T...>* v) noexcept;
template<size_t I, class... T>
  constexpr add_pointer_t<const variant_alternative_t<I, variant<T...>>>
    get_if(const variant<T...>* v) noexcept;

template<class U, class... T>
  constexpr add_pointer_t<U>
    get_if(variant<T...>* v) noexcept;
template<class U, class... T>
  constexpr add_pointer_t<const U>
    get_if(const variant<T...>* v) noexcept;

// unsafe_get (extension)

template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&
    unsafe_get(variant<T...>& v);
template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&&
    unsafe_get(variant<T...>&& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&
    unsafe_get(const variant<T...>& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&&
    unsafe_get(const variant<T...>&& v);

// relational operators

template<class... T>
  constexpr bool operator==(const variant<T...>& v, const variant<T...>& w);
template<class... T>
  constexpr bool operator!=(const variant<T...>& v, const variant<T...>& w);
template<class... T>
  constexpr bool operator<(const variant<T...>& v, const variant<T...>& w);
template<class... T>
  constexpr bool operator>(const variant<T...>& v, const variant<T...>& w);
template<class... T>
  constexpr bool operator<=(const variant<T...>& v, const variant<T...>& w);
template<class... T>
  constexpr bool operator>=(const variant<T...>& v, const variant<T...>& w);

// swap

template<class... T>
  void swap(variant<T...>& v, variant<T...>& w) noexcept( /*see below*/ );

// visit

template<class R = /*unspecified*/, class F, class... V>
  constexpr /*see below*/ visit(F&& f, V&&... v);

// visit_by_index (extension)

template<class R = /*unspecified*/, class V, class... F>
  constexpr /*see below*/ visit_by_index(V&& v, F&&... f);

// monostate

struct monostate {};

constexpr bool operator==(monostate, monostate) noexcept { return true; }
constexpr bool operator!=(monostate, monostate) noexcept { return false; }
constexpr bool operator<(monostate, monostate) noexcept { return false; }
constexpr bool operator>(monostate, monostate) noexcept { return false; }
constexpr bool operator<=(monostate, monostate) noexcept { return true; }
constexpr bool operator>=(monostate, monostate) noexcept { return true; }

// stream insertion (extension)

template<class Ch, class Tr, class... T>
  std::basic_ostream<Ch, Tr>&
    operator<<( std::basic_ostream<Ch, Tr>& os, variant<T...> const& v );

template<class Ch, class Tr>
  std::basic_ostream<Ch, Tr>&
    operator<<( std::basic_ostream<Ch, Tr>& os, monostate const& v );

// bad_variant_access

class bad_variant_access;

} // namespace variant2
} // namespace boost

variant

namespace boost {
namespace variant2 {

template<class... T> class variant
{
public:

  // constructors

  constexpr variant() noexcept( /*see below*/ );

  constexpr variant( variant const & r ) noexcept( /*see below*/ );
  constexpr variant( variant&& r ) noexcept( /*see below*/ );

  template<class U>
    constexpr variant( U&& u ) noexcept( /*see below*/ );

  template<class U, class... A>
    constexpr explicit variant( in_place_type_t<U>, A&&... a );
  template<class U, class V, class... A>
    constexpr explicit variant( in_place_type_t<U>,
      std::initializer_list<V> il, A&&... a );

  template<size_t I, class... A>
    constexpr explicit variant( in_place_index_t<I>, A&&... a );
  template<size_t I, class V, class... A>
    constexpr explicit variant( in_place_index_t<I>,
      std::initializer_list<V> il, A&&... a );

  // destructor

  ~variant();

  // assignment

  constexpr variant& operator=( variant const & r ) noexcept( /*see below*/ );
  constexpr variant& operator=( variant&& r ) noexcept( /*see below*/ );

  template<class U> constexpr variant& operator=( U&& u ) noexcept( /*see below*/ );

  // modifiers

  template<class U, class... A>
    constexpr U& emplace( A&&... a );
  template<class U, class V, class... A>
    constexpr U& emplace( std::initializer_list<V> il, A&&... a );

  template<size_t I, class... A>
    constexpr variant_alternative_t<I, variant<T...>>&
      emplace( A&&... a );
  template<size_t I, class V, class... A>
    constexpr variant_alternative_t<I, variant<T...>>&
      emplace( std::initializer_list<V> il, A&&... a );

  // value status

  constexpr bool valueless_by_exception() const noexcept;
  constexpr size_t index() const noexcept;

  static constexpr bool uses_double_storage() noexcept;

  // swap

  void swap( variant& r ) noexcept( /*see below*/ );

  // converting constructors (extension)

  template<class... U> variant( variant<U...> const& r )
    noexcept( /*see below*/ );

  template<class... U> variant( variant<U...>&& r )
    noexcept( /*see below*/ );

  // subset (extension)

  template<class... U> constexpr variant<U...> subset() & ;
  template<class... U> constexpr variant<U...> subset() && ;
  template<class... U> constexpr variant<U...> subset() const& ;
  template<class... U> constexpr variant<U...> subset() const&& ;
};

} // namespace variant2
} // namespace boost

在接下来的描述中,设 i 的范围为 [0, sizeof…​(T)),而 TiT…​ 中的第 i 个类型。

构造函数
constexpr variant() noexcept( std::is_nothrow_default_constructible_v<T0> );
  • 效果

    构造一个 variant,其中包含类型 T0 的值初始化值。

    确保

    index() == 0.

    抛出

    T0 的值初始化可能抛出的任何异常。

    备注

    除非 std::is_default_constructible_v<T0>true,否则此函数不参与重载解析。

constexpr variant( variant const & w )
  noexcept( mp_all<std::is_nothrow_copy_constructible<T>...>::value );
  • 效果

    将 variant 初始化为包含与 w 相同的备选项和值。

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非对所有 istd::is_copy_constructible_v<Ti>true,否则此函数不参与重载解析。

constexpr variant( variant&& w )
  noexcept( mp_all<std::is_nothrow_move_constructible<T>...>::value );
  • 效果

    将 variant 初始化为包含与 w 相同的备选项和值。

    抛出

    包含值的移动初始化可能抛出的任何异常。

    备注

    除非对所有 istd::is_move_constructible_v<Ti>true,否则此函数不参与重载解析。

template<class U> constexpr variant( U&& u ) noexcept(/*see below*/);
  • Tj 是按如下方式确定的类型:为每个备选项类型 Ti 构建一个假设的函数 FUN(Ti)。由重载解析为表达式 FUN(std::forward<U>(u)) 选择的 FUN(Tj) 重载定义了备选项 Tj,即构造后的包含值的类型。

    效果

    *this 初始化为包含备选项类型 Tj,并从 std::forward<U>(u) 初始化包含的值。

    确保

    holds_alternative<Tj>(*this).

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    noexcept 中的表达式等同于 std::is_nothrow_constructible_v<Tj, U>。除非满足以下条件,否则此函数不参与重载解析:

    • sizeof…​(T) 非零,

    • std::is_same_v<std::remove_cvref_t<U>, variant>false

    • std::remove_cvref_t<U> 既不是 in_place_type_t 的特化也不是 in_place_index_t 的特化,

    • std::is_constructible_v<Tj, U>true,并且

    • 表达式 FUN(std::forward<U>(u)) 是格式正确的。

template<class U, class... A>
  constexpr explicit variant( in_place_type_t<U>, A&&... a );
  • 效果

    使用参数 std::forward<A>(a)…​ 初始化类型为 U 的包含值。

    确保

    holds_alternative<U>(*this).

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 UT…​ 中恰好出现一次,并且 std::is_constructible_v<U, A…​>true,否则此函数不参与重载解析。

template<class U, class V, class... A>
  constexpr explicit variant( in_place_type_t<U>, std::initializer_list<V> il,
    A&&... a );
  • 效果

    使用参数 ilstd::forward<A>(a)…​ 初始化类型为 U 的包含值。

    确保

    holds_alternative<U>(*this).

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 UT…​ 中恰好出现一次,并且 std::is_constructible_v<U, initializer_list<V>&, A…​>true,否则此函数不参与重载解析。

template<size_t I, class... A>
  constexpr explicit variant( in_place_index_t<I>, A&&... a );
  • 效果

    使用参数 std::forward<A>(a)…​ 初始化类型为 TI 的包含值。

    确保

    index() == I.

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 I < sizeof…​(T)std::is_constructible_v<TI, A…​>true,否则此函数不参与重载解析。

template<size_t I, class V, class... A>
  constexpr explicit variant( in_place_index_t<I>, std::initializer_list<V> il,
    A&&... a );
  • 效果

    使用参数 ilstd::forward<A>(a)…​ 初始化类型为 TI 的包含值。

    确保

    index() == I.

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 I < sizeof…​(T)std::is_constructible_v<TI, initializer_list<V>&, A…​>true,否则此函数不参与重载解析。

析构函数
~variant();
  • 效果

    销毁当前包含的值。

赋值
constexpr variant& operator=( const variant& r )
  noexcept( mp_all<std::is_nothrow_copy_constructible<T>...>::value );
  • jr.index()

    效果

    emplace<j>(get<j>(r)).

    返回

    *this.

    确保

    index() == r.index().

    备注

    除非对所有 istd::is_copy_constructible_v<Ti> && std::is_copy_assignable_v<Ti>true,否则此运算符不参与重载解析。

constexpr variant& operator=( variant&& r )
  noexcept( mp_all<std::is_nothrow_move_constructible<T>...>::value );
  • jr.index()

    效果

    emplace<j>(get<j>(std::move(r))).

    返回

    *this.

    确保

    index() == r.index().

    备注

    除非对所有 istd::is_move_constructible_v<Ti> && std::is_move_assignable_v<Ti>true,否则此运算符不参与重载解析。

template<class U> constexpr variant& operator=( U&& u )
  noexcept( /*see below*/ );
  • Tj 是按如下方式确定的类型:为每个备选项类型 Ti 构建一个假设的函数 FUN(Ti)。由重载解析为表达式 FUN(std::forward<U>(u)) 选择的 FUN(Tj) 重载定义了备选项 Tj,即构造后的包含值的类型。

    效果

    emplace<j>(std::forward<U>(u)).

    返回

    *this.

    确保

    index() == j.

    备注

    noexcept 中的表达式为 std::is_nothrow_constructible_v<Tj, U&&>。除非满足以下条件,否则此运算符不参与重载解析:

    • std::is_same_v<std::remove_cvref_t<T>, variant>false

    • std::is_constructible_v<Tj, U&&> && std::is_assignable_v<Tj&, U&&>true,并且

    • 表达式 FUN(std::forward<U>(u))(其中 FUN 是上述假设函数集)是格式正确的。

修改器
template<class U, class... A>
  constexpr U& emplace( A&&... a );
  • IUT…​ 中的零基索引。

    效果

    等同于:return emplace<I>(std::forward<A>(a)…​);

    备注

    除非 std::is_constructible_v<U, A&&…​>trueUT…​ 中只出现一次,否则此函数不参与重载解析。

template<class U, class V, class... A>
  constexpr U& emplace( std::initializer_list<V> il, A&&... a );
  • IUT…​ 中的零基索引。

    效果

    等同于:return emplace<I>(il, std::forward<A>(a)…​);

    备注

    除非 std::is_constructible_v<U, std::initializer_list<V>&, A&&…​>trueUT…​ 中只出现一次,否则此函数不参与重载解析。

template<size_t I, class... A>
  constexpr variant_alternative_t<I, variant<T...>>&
    emplace( A&&... a );
  • 要求

    I < sizeof…​(T).

    效果

    初始化一个新的包含值,如同使用表达式 Ti(std::forward<A>(a)…​),然后销毁当前包含的值。

    确保

    index() == I.

    返回

    对新包含值的一个引用。

    抛出

    除非新包含值的初始化抛出异常,否则无。

    异常安全性

    强。在异常情况下,包含的值保持不变。

    备注

    除非 std::is_constructible_v<Ti, A&&…​>true,否则此函数不参与重载解析。

template<size_t I, class V, class... A>
  constexpr variant_alternative_t<I, variant<T...>>&
    emplace( std::initializer_list<V> il, A&&... a );
  • 要求

    I < sizeof…​(T).

    效果

    初始化一个新的包含值,如同使用表达式 Ti(il, std::forward<A>(a)…​),然后销毁当前包含的值。

    确保

    index() == I.

    返回

    对新包含值的一个引用。

    抛出

    除非新包含值的初始化抛出异常,否则无。

    异常安全性

    强。在异常情况下,包含的值保持不变。

    备注

    除非 std::is_constructible_v<Ti, std::initializer_list<V>&, A&&…​>true,否则此函数不参与重载解析。

值状态
constexpr bool valueless_by_exception() const noexcept;
  • 返回

    false.

注意
此函数仅为与 std::variant 兼容而提供。
constexpr size_t index() const noexcept;
  • 返回

    当前备选项的零基索引。

static constexpr bool uses_double_storage() noexcept;
  • 返回

    如果 variant 使用双存储来满足永不“无值”的保证,因为其中一个备选项不是非抛出移动构造的,则返回 true,否则返回 false

Swap
void swap( variant& r ) noexcept( mp_all<std::is_nothrow_move_constructible<T>...,
  is_nothrow_swappable<T>...>::value );
  • 效果
    • 如果 index() == r.index(),则调用 swap(get<I>(*this), get<I>(r)),其中 Iindex()

    • 否则,如同 variant tmp(std::move(*this)); *this = std::move(r); r = std::move(tmp);

转换构造函数(扩展)
template<class... U> variant( variant<U...> const& r )
  noexcept( mp_all<std::is_nothrow_copy_constructible<U>...>::value );
  • 效果

    r 的包含值初始化包含值。

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 U…​ 中的所有类型都在 T…​ 中,并且对所有 Uistd::is_copy_constructible_v<Ui>::valuetrue,否则此函数不参与重载解析。

template<class... U> variant( variant<U...>&& r )
  noexcept( mp_all<std::is_nothrow_move_constructible<U>...>::value );
  • 效果

    std::move(r) 的包含值初始化包含值。

    抛出

    包含值初始化可能抛出的任何异常。

    备注

    除非 U…​ 中的所有类型都在 T…​ 中,并且对所有 Uistd::is_move_constructible_v<Ui>::valuetrue,否则此函数不参与重载解析。

子集(扩展)
template<class... U> constexpr variant<U...> subset() & ;
template<class... U> constexpr variant<U...> subset() const& ;
  • 返回

    一个 variant<U…​>,其包含值由 *this 的包含值进行复制初始化,并且具有相同的类型。

    抛出
    • 如果 *this 的当前备选项不在 U…​ 的类型中,则为 bad_variant_access

    • 否则,为包含值初始化可能抛出的任何异常。

    备注

    除非 U…​ 中的所有类型都在 T…​ 中,并且对所有 Uistd::is_copy_constructible_v<Ui>::valuetrue,否则此函数不参与重载解析。

template<class... U> constexpr variant<U...> subset() && ;
template<class... U> constexpr variant<U...> subset() const&& ;
  • 返回

    一个 variant<U…​>,其包含值由 *this 的包含值进行移动初始化,并且具有相同的类型。

    抛出
    • 如果 *this 的当前备选项不在 U…​ 的类型中,则为 bad_variant_access

    • 否则,为包含值初始化可能抛出的任何异常。

    备注

    除非 U…​ 中的所有类型都在 T…​ 中,并且对所有 Uistd::is_move_constructible_v<Ui>::valuetrue,否则此函数不参与重载解析。

variant_alternative

template<size_t I, class T> struct variant_alternative<I, T const>;
template<size_t I, class T> struct variant_alternative<I, T volatile>;
template<size_t I, class T> struct variant_alternative<I, T const volatile>;
template<size_t I, class T> struct variant_alternative<I, T&>; // extension
template<size_t I, class T> struct variant_alternative<I, T&&>; // extension
  • 如果 typename variant_alternative<I, T>::type 存在且为 U

    • variant_alternative<I, T const>::typeU const

    • variant_alternative<I, T volatile>::typeU volatile

    • variant_alternative<I, T const volatile>::typeU const volatile

    • variant_alternative<I, T&>::typeU&

    • variant_alternative<I, T&&>::typeU&&

    否则,这些结构没有成员 type

template<size_t I, class... T>
  struct variant_alternative<I, variant<T...>>;
  • I < sizeof…​(T) 时,嵌套类型 typeT…​ 中第 I 个(零基)类型的别名。否则,没有成员 type

holds_alternative

template<class U, class... T>
  constexpr bool holds_alternative(const variant<T...>& v) noexcept;
  • 要求

    类型 UT…​ 中恰好出现一次。否则,程序格式错误。

    返回

    如果 index() 等于 UT…​ 中的零基索引,则为 true

get

template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&
    get(variant<T...>& v);
template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&&
    get(variant<T...>&& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&
    get(const variant<T...>& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&&
    get(const variant<T...>&& v);
  • 效果

    如果 v.index() == I,则返回对存储在 variant 中的对象的引用。否则,抛出 bad_variant_access

    备注

    除非 I < sizeof…​(T),否则这些函数不参与重载解析。

template<class U, class... T>
  constexpr U& get(variant<T...>& v);
template<class U, class... T>
  constexpr U&& get(variant<T...>&& v);
template<class U, class... T>
  constexpr const U& get(const variant<T...>& v);
template<class U, class... T>
  constexpr const U&& get(const variant<T...>&& v);
  • 要求

    类型 UT…​ 中恰好出现一次。否则,程序格式错误。

    效果

    如果 v 存储类型为 U 的值,则返回对该值的引用。否则,抛出 bad_variant_access

get_if

template<size_t I, class... T>
  constexpr add_pointer_t<variant_alternative_t<I, variant<T...>>>
    get_if(variant<T...>* v) noexcept;
template<size_t I, class... T>
  constexpr add_pointer_t<const variant_alternative_t<I, variant<T...>>>
    get_if(const variant<T...>* v) noexcept;
  • 效果

    如果 v != nullptr && v->index() == I,则返回指向存储在 variant 中的值的指针。否则,为 nullptr

    备注

    除非 I < sizeof…​(T),否则这些函数不参与重载解析。

template<class U, class... T>
  constexpr add_pointer_t<U>
    get_if(variant<T...>* v) noexcept;
template<class U, class... T>
  constexpr add_pointer_t<const U>
    get_if(const variant<T...>* v) noexcept;
  • 要求

    类型 UT…​ 中恰好出现一次。否则,程序格式错误。

    效果

    等同于:return get_if<I>(v); 其中 IUT…​ 中的零基索引。

unsafe_get(扩展)

template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&
    unsafe_get(variant<T...>& v);
template<size_t I, class... T>
  constexpr variant_alternative_t<I, variant<T...>>&&
    unsafe_get(variant<T...>&& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&
    unsafe_get(const variant<T...>& v);
template<size_t I, class... T>
  constexpr const variant_alternative_t<I, variant<T...>>&&
    unsafe_get(const variant<T...>&& v);
  • 要求

    v.index() == I.

    返回

    对存储在 variant 中的对象的引用。

关系运算符

template<class... T>
  constexpr bool operator==(const variant<T...>& v, const variant<T...>& w);
  • 返回

    v.index() == w.index() && get<I>(v) == get<I>(w),其中 Iv.index()

template<class... T>
  constexpr bool operator!=(const variant<T...>& v, const variant<T...>& w);
  • 返回

    !(v == w).

template<class... T>
  constexpr bool operator<(const variant<T...>& v, const variant<T...>& w);
  • 返回

    v.index() < w.index() || (v.index() == w.index() && get<I>(v) < get<I>(w)),其中 Iv.index()

template<class... T>
  constexpr bool operator>(const variant<T...>& v, const variant<T...>& w);
  • 返回

    w < v.

template<class... T>
  constexpr bool operator<=(const variant<T...>& v, const variant<T...>& w);
  • 返回

    v.index() < w.index() || (v.index() == w.index() && get<I>(v) <= get<I>(w)),其中 Iv.index()

template<class... T>
  constexpr bool operator>=(const variant<T...>& v, const variant<T...>& w);
  • 返回

    w <= v.

交换

template<class... T>
  void swap(variant<T...>& v, variant<T...>& w) noexcept( /*see below*/ );
  • 效果

    等同于 v.swap(w)

visit

template<class R = /*unspecified*/, class F, class... V>
  constexpr /*see below*/ visit(F&& f, V&&... v);
  • 返回

    std::forward<F>(f)(get<I>(std::forward<V>(v))…​),其中 I…​v.index()…​

    备注

    如果显式给出了 R,例如 visit<int>,则返回类型为 R。否则,它从 F 推导。F 应用于 variant 备选项的所有可能情况必须具有相同的返回类型才能成功推导。

visit_by_index(扩展)

template<class R = /*unspecified*/, class V, class... F>
  constexpr /*see below*/ visit_by_index(V&& v, F&&... f);
  • 要求

    variant_size<V>::value == sizeof…​(F),否则程序格式错误。

    返回

    std::forward<Fi>(fi)(get<i>(std::forward<V>(v))),其中 iv.index(),而 Fifi 分别是 F…​f…​ 的第 i 个元素。

    备注

    如果显式给出了 R,例如 visit_by_index<int>,则返回类型为 R。否则,它从 F…​V 推导。Fi 应用于相应 variant 备选项的所有情况必须具有相同的返回类型才能成功推导。

流插入(扩展)

template<class Ch, class Tr, class... T>
  std::basic_ostream<Ch, Tr>&
    operator<<( std::basic_ostream<Ch, Tr>& os, variant<T...> const& v );
  • 要求

    sizeof…​(T) != 0.

    返回

    os << get<I>(v),其中 Iv.index()

template<class Ch, class Tr>
  std::basic_ostream<Ch, Tr>&
    operator<<( std::basic_ostream<Ch, Tr>& os, monostate const& v );
  • 效果

    os << "monostate".

    返回

    os.

bad_variant_access

class bad_variant_access: public std::exception
{
public:

    bad_variant_access() noexcept = default;

    char const * what() const noexcept
    {
        return "bad_variant_access";
    }
};

<boost/variant2.hpp>

此便利头文件包含 <boost/variant2/variant.hpp>

本文档版权归 2018、2019 Peter Dimov 所有,并根据 Boost Software License, Version 1.0 分发。