Namespaces
Variants

std::variant<类型...>::operator=

来自 zh.cppreference.net
 
 
 
 
constexpr variant& operator=( const variant& rhs );
(1) (C++17 起)
constexpr variant& operator=( variant&& rhs ) noexcept(/* see below */);
(2) (C++17 起)
template< class T >
variant& operator=( T&& t ) noexcept(/* see below */);
(3) (C++17 起)
(C++20 起为 constexpr)

为现有 variant 对象赋予新值。

1) 复制赋值:
  • 如果 *this 与 rhs 均为因异常无值,则不进行操作。
  • 否则,若 rhs 无值,但 *this 有值,则销毁 *this 所含的值并使其变为无值状态。
  • 否则,如果 rhs 持有与 *this 相同的可选项,则将 rhs 所含的值赋给 *this 所含的值。若抛出异常,*this 不会变为无值:其值取决于该可选项复制赋值的异常安全保证。
  • 否则,如果 rhs 持有的可选项不是 noexcept 可复制构造,或者 不是 noexcept 可移动构造(分别由 std::is_nothrow_copy_constructible 和 std::is_nothrow_move_constructible 确定),则等价于 this->emplace<rhs.index()>(*std::get_if<rhs.index()>(std::addressof(rhs)))。若在 *this 内部的复制构造过程中抛出异常,则 valueless_by_exception 可能变为 emplace 状态。
  • 否则,等价于 this->operator=(variant(rhs))。
除非对于 std::is_copy_constructible_v<T_i> 和 std::is_copy_assignable_v<T_i> 中的所有 true,T_i 均为 Types...,否则此重载被定义为弃置。如果对于所有 std::is_trivially_copy_constructible_v<T_i> 中的 std::is_trivially_copy_assignable_v<T_i>,std::is_trivially_destructible_v<T_i>、true 和 T_i 均为 Types...,则此重载为平凡。
2) 移动赋值:
  • 如果 *this 和 rhs 均为因异常无值,则不进行操作。
  • 否则,若 rhs 无值,但 *this 有值,则销毁 *this 所含的值并使其变为无值状态。
  • 否则,如果 rhs 持有与 *this 相同的可选项,则将 std::move(*std::get_if<j>(std::addressof(rhs))) 赋给 *this 所含的值,其中 j 为 index()。若抛出异常,*this 不会变为无值:其值取决于该可选项移动赋值的异常安全保证。
  • 否则(如果 rhs 与 *this 持有不同的可选项),则等价于 this->emplace<rhs.index()>(std::move(*std::get_if<rhs.index()>(std::addressof(rhs))))。如果 T_i 的移动构造函数抛出异常,*this 会变为 valueless_by_exception 状态。
此重载仅当对于所有 std::is_move_constructible_v<T_i> 中的 std::is_move_assignable_v<T_i>,true 与 T_i 均为 Types... 时才参与重载决议。如果对于所有 std::is_trivially_move_constructible_v<T_i> 中的 std::is_trivially_move_assignable_v<T_i>,std::is_trivially_destructible_v<T_i>、true 和 T_i 均为 Types...,则此重载为平凡。
3) 转换赋值。
  • 确定可选项类型 T_j,其方式如同通过重载决议选择表达式 F(std::forward<T>(t)),假设存在对每个 F(T_i) 类型(来自T_i)的虚构函数 Types... 的重载同时在作用域内,但有以下例外:
  • 仅当对于某个发明的变量 F(T_i),声明 T_i x[] = { std::forward<T>(t) }; 有效时,才考虑重载 x;
  • 如果 *this 已经持有 T_j 类型,则将 std::forward<T>(t) 赋给 *this 所含的值。若抛出异常,*this 不会变为无值:其值取决于所调用赋值的异常安全保证。
  • 否则,如果 std::is_nothrow_constructible_v<T_j, T> || !std::is_nothrow_move_constructible_v<T_j> 是 true,则等价于 this->emplace<j>(std::forward<T>(t))。若在 *this 内部的初始化过程中抛出异常,则 valueless_by_exception 可能变为 emplace 状态。
  • 否则,等价于 this->emplace<j>(T_j(std::forward<T>(t)))。

此重载仅当满足以下所有条件时才参与重载决议:std::decay_t<T>(C++20 前)std::remove_cvref_t<T>(C++20 起) 与 variant 不是同一类型,并且 std::is_assignable_v<T_j&, T> 为 true,并且 std::is_constructible_v<T_j, T> 为 true,并且表达式 F(std::forward<T>(t))(其中 F 是上述虚构函数集合)是良构的。

std::variant<std::string> v1;
v1 = "abc"; // OK
std::variant<std::string, std::string> v2;
v2 = "abc"; // Error
std::variant <std::string, bool> v3;
v3 = "abc"; // OK, chooses string; bool is not a candidate
std::variant<float, long, double> v4; // holds float
v4 = 0; // OK, holds long; float and double are not candidates

参数

rhs - 另一个 variant
t - 可转换为 variant 任一备选类型的值

返回值

* this

异常

1) 可能抛出任何由赋值操作以及任何备选类型的拷贝/移动初始化所抛出的异常。
2)
noexcept 规范:
noexcept ( ( ( std:: is_nothrow_move_constructible_v < Types > &&
std:: is_nothrow_move_assignable_v < Types > ) && ... ) )
3)

注释

功能测试 宏 值 标准 特性
__cpp_lib_variant 202106L (C++20)
(DR)
完全 constexpr 化的 std::variant ( 3 )

示例

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
std::ostream& operator<<(std::ostream& os, std::variant<int, std::string> const& va)
{
    os << ": { ";
    std::visit([&](auto&& arg)
    {
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<T, int>)
            os << arg;
        else if constexpr (std::is_same_v<T, std::string>)
            os << std::quoted(arg);
    }, va);
    return os << " };\n";
}
int main()
{
    std::variant<int, std::string> a{2017}, b{"CppCon"};
    std::cout << "a" << a << "b" << b << '\n';
    std::cout << "(1) operator=( const variant& rhs )\n";
    a = b;
    std::cout << "a" << a << "b" << b << '\n';
    std::cout << "(2) operator=( variant&& rhs )\n";
    a = std::move(b);
    std::cout << "a" << a << "b" << b << '\n';
    std::cout << "(3) operator=( T&& t ), where T is int\n";
    a = 2019;
    std::cout << "a" << a << '\n';
    std::cout << "(3) operator=( T&& t ), where T is std::string\n";
    std::string s{"CppNow"};
    std::cout << "s: " << std::quoted(s) << '\n';
    a = std::move(s);
    std::cout << "a" << a << "s: " << std::quoted(s) << '\n';
}

可能的输出:

a: { 2017 };
b: { "CppCon" };
(1) operator=( const variant& rhs )
a: { "CppCon" };
b: { "CppCon" };
(2) operator=( variant&& rhs )
a: { "CppCon" };
b: { "" };
(3) operator=( T&& t ), where T is int
a: { 2019 };
(3) operator=( T&& t ), where T is std::string
s: "CppNow"
a: { "CppNow" };
s: ""

缺陷报告

下列行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。

缺陷报告 应用于 发布时的行为 正确行为
LWG 3024 C++17 若任何成员类型不可复制时
拷贝赋值运算符不参与重载决议
改为定义为删除状态
LWG 3585 C++17 由于没有可用的移动赋值操作
转换赋值有时意外地形成病式表达式
改为良构
P0602R4 C++17 即使底层操作是平凡的
拷贝/移动赋值可能不是平凡的
要求传播平凡性
P0608R3 C++17 转换赋值盲目组合重载集
导致非预期的转换
不考虑窄化和布尔转换
P2231R1 C++20 转换赋值 ( 3 ) 不是 constexpr
而在 C++20 中所需操作可以是 constexpr
改为 constexpr

参见

在 variant 中就地构造值
(公开成员函数)