概述
描述
此库实现了一个类型安全的、带标签的联合(discriminated/tagged union)类型 variant<T…>,其 API 与 C++17 标准中的 std::variant<T…> 兼容。
variant<T1, T2, …, Tn> 变量可以存储 T1、T2、…、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 Rectangle 和 new 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)构造,并且确实可以。它也可以被赋值一个 Circle 或 Rectangle。
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) 将返回 int,get<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
}
}
访问
最后但同样重要的是 visit。visit(f, v) 用 variant v 中包含的值调用函数对象 f 并返回结果。当 v 是 variant<int, float> 时,它将用 int 或 float 调用 f。函数对象必须准备好接受两者。
实际上,这可以通过让函数接受可以传递 int 或 float 的类型来实现,例如 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 也可以接受多个 variant。visit(f, v1, v2) 调用 f(x1, x2),其中 x1 是 v1 中包含的值,x2 是 v2 中包含的值。
默认构造
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_from和boost::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::hash、boost::hash的支持。 -
variant<T…>现在是平凡的(trivial),当T…中的所有类型都是平凡的。这通过使其能够通过寄存器传递和返回来提高性能。
1.71.0 版中的更改
经过 Boost 形式评审后,实现已更改为提供强异常安全保证,而不是基本保证。已移除 expected。
设计
功能
此 variant 实现有两个显著特点:
-
它永不“无值”,即
variant<T1, T2, …, Tn>始终包含T1、T2、…、Tn中某个类型的有效值。 -
它在赋值和
emplace时提供强异常安全保证。
这是通过使用双存储实现的,除非所有包含的类型都具有非抛出移动构造函数。
基本原理
永不“无值”
从直观上看,variant<X, Y, Z> 只能存储 X、Y 或 Z 类型的值,除此之外不能存储其他任何值。
如果我们把 variant 看作是 union 的扩展,因为 union 有一个名为“无活动成员”的状态,所以可以说 variant<X, Y, Z> 也应该有一个额外的状态,不包含 X、Y、Z 中的任何一个。
然而,这使得 variant 在实践中不太方便,作为构建块的用途也更少。如果我们确实需要一个只存储 X、Y 或 Z 的变量,额外的空状态会带来需要解决的复杂性。并且在需要这个额外的空状态的情况下,我们可以直接使用 variant<empty, X, Y, Z>,其中 empty 是一个合适的 struct empty {};。
从纯粹的设计角度来看,没有额外空状态的论证是坚实的。然而,实现上的考虑则相反。
当我们用另一个值(类型为 Y)替换 variant 的当前值(例如类型为 X)时,因为新值需要占用与旧值相同的存储空间,所以我们必须先销毁旧的 X,然后在其位置构造一个新的 Y。但由于这是 C++,构造可能会抛出异常。此时 variant 处于我们已经同意它不能处于的“无活动成员”状态。
这是一个合法的问题,正是这个问题使得拥有空/无值状态如此有吸引力。我们只需在异常时将 variant 留空即可。
如上所述,从设计角度来看,这是不可取的,因为它降低了组件的实用性和优雅性。
有几种方法可以解决这个问题。最直接的方法是禁止构造可能抛出异常的类型。因为我们总是可以先创建一个临时值,然后使用移动构造函数来初始化 variant 中的值,所以要求非抛出移动构造函数就足够了,而不是要求所有构造函数都非抛出。
不幸的是,在至少一种流行的标准库实现中,基于节点的容器(如 std::list 和 std::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 上提供强异常安全保证被宣布为接受条件。
回想起来,这是一个正确的决定。通常不提供强保证的原因是它无法组合。当 X 和 Y 在赋值时提供基本保证时,struct { X x; Y y; }; 也提供基本保证。同样,当 X 和 Y 具有非抛出赋值时,该 struct 也具有非抛出赋值。但这不适用于强保证。
通常的做法是在赋值时提供基本保证,让用户通过非抛出 swap 或非抛出移动赋值来合成“强”赋值。也就是说,给定类型为 X 的 x1 和 x2,而不是“基本”的 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,作为get和get_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 Actions 和 Appveyor 上进行了测试。
参考
<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)),而 Ti 是 T… 中的第 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相同的备选项和值。 - 抛出
-
包含值初始化可能抛出的任何异常。
- 备注
-
除非对所有
i,std::is_copy_constructible_v<Ti>为true,否则此函数不参与重载解析。
constexpr variant( variant&& w )
noexcept( mp_all<std::is_nothrow_move_constructible<T>...>::value );
-
- 效果
-
将 variant 初始化为包含与
w相同的备选项和值。 - 抛出
-
包含值的移动初始化可能抛出的任何异常。
- 备注
-
除非对所有
i,std::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). - 抛出
-
包含值初始化可能抛出的任何异常。
- 备注
-
除非
U在T…中恰好出现一次,并且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 );
-
- 效果
-
使用参数
il和std::forward<A>(a)…初始化类型为U的包含值。 - 确保
-
holds_alternative<U>(*this). - 抛出
-
包含值初始化可能抛出的任何异常。
- 备注
-
除非
U在T…中恰好出现一次,并且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 );
-
- 效果
-
使用参数
il和std::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 );
-
设
j为r.index()。- 效果
-
emplace<j>(get<j>(r)). - 返回
-
*this. - 确保
-
index() == r.index(). - 备注
-
除非对所有
i,std::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 );
-
设
j为r.index()。- 效果
-
emplace<j>(get<j>(std::move(r))). - 返回
-
*this. - 确保
-
index() == r.index(). - 备注
-
除非对所有
i,std::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 );
-
设
I是U在T…中的零基索引。- 效果
-
等同于:
return emplace<I>(std::forward<A>(a)…); - 备注
-
除非
std::is_constructible_v<U, A&&…>为true且U在T…中只出现一次,否则此函数不参与重载解析。
template<class U, class V, class... A>
constexpr U& emplace( std::initializer_list<V> il, A&&... a );
-
设
I是U在T…中的零基索引。- 效果
-
等同于:
return emplace<I>(il, std::forward<A>(a)…); - 备注
-
除非
std::is_constructible_v<U, std::initializer_list<V>&, A&&…>为true且U在T…中只出现一次,否则此函数不参与重载解析。
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)),其中I是index()。 -
否则,如同
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…中,并且对所有Ui,std::is_copy_constructible_v<Ui>::value为true,否则此函数不参与重载解析。
template<class... U> variant( variant<U...>&& r )
noexcept( mp_all<std::is_nothrow_move_constructible<U>...>::value );
-
- 效果
-
从
std::move(r)的包含值初始化包含值。 - 抛出
-
包含值初始化可能抛出的任何异常。
- 备注
-
除非
U…中的所有类型都在T…中,并且对所有Ui,std::is_move_constructible_v<Ui>::value为true,否则此函数不参与重载解析。
子集(扩展)
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…中,并且对所有Ui,std::is_copy_constructible_v<Ui>::value为true,否则此函数不参与重载解析。
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…中,并且对所有Ui,std::is_move_constructible_v<Ui>::value为true,否则此函数不参与重载解析。
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>::type为U const; -
variant_alternative<I, T volatile>::type为U volatile; -
variant_alternative<I, T const volatile>::type为U const volatile。 -
variant_alternative<I, T&>::type为U&。 -
variant_alternative<I, T&&>::type为U&&。
否则,这些结构没有成员
type。 -
template<size_t I, class... T>
struct variant_alternative<I, variant<T...>>;
-
当
I < sizeof…(T)时,嵌套类型type是T…中第I个(零基)类型的别名。否则,没有成员type。
holds_alternative
template<class U, class... T>
constexpr bool holds_alternative(const variant<T...>& v) noexcept;
-
- 要求
-
类型
U在T…中恰好出现一次。否则,程序格式错误。 - 返回
-
如果
index()等于U在T…中的零基索引,则为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);
-
- 要求
-
类型
U在T…中恰好出现一次。否则,程序格式错误。 - 效果
-
如果
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;
-
- 要求
-
类型
U在T…中恰好出现一次。否则,程序格式错误。 - 效果
-
等同于:
return get_if<I>(v);其中I是U在T…中的零基索引。
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),其中I是v.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)),其中I是v.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)),其中I是v.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))),其中i是v.index(),而Fi和fi分别是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),其中I是v.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 分发。