跳到主要内容

std::variant<Types...>::operator=

// 1)
constexpr variant& operator=( const variant& rhs );
// 2)
constexpr variant& operator=( variant&& rhs ) noexcept(/* see below */);
// 3)
template< class T >
constexpr variant& operator=( T&& t ) noexcept(/* see below */);

将新值赋给现有变体对象。


复制赋值

  • 如果 *this 和 rhs 都因异常而无值,则不执行任何操作。
  • 否则,如果 rhs 无值,但 *this 有值,则销毁 *this 中包含的值并使其无值。
  • 否则,如果 rhs 持有与 *this 相同的候选项,则将 rhs 中包含的值赋给 *this 中包含的值。如果抛出异常,*this 不会变为无值:值取决于候选项复制赋值的异常安全保证。
  • 否则,如果 rhs 持有的候选项是 nothrow 可复制构造的或不是 nothrow 可移动构造的(分别由 std::is_nothrow_copy_constructiblestd::is_nothrow_move_constructible 确定),则等效于 this->emplace<rhs.index()>(get<rhs.index()>(rhs))。如果在 emplace 内部的复制构造中抛出异常,*this 可能会变为 valueless_by_exception
  • 否则,等效于 this->operator=(variant(rhs))

除非 Types... 中的所有 T_i 都满足 std::is_copy_constructible_v<T_i>std::is_copy_assignable_v<T_i> 为真,否则此重载被定义为已删除。如果 Types... 中的所有 T_i 都满足 std::is_trivially_copy_constructible_v<T_i>std::is_trivially_copy_assignable_v<T_i>std::is_trivially_destructible_v<T_i> 为真,则此重载是平凡的。


移动赋值

  • 如果 *thisrhs 都因异常而无值,则不执行任何操作。
  • 否则,如果 rhs 无值,但 *this 有值,则销毁 *this 中包含的值并使其无值。
  • 否则,如果 rhs 持有与 *this 相同的候选项,则将 std::get<j>(std::move(rhs)) 赋给 *this 中包含的值,其中 jindex()。如果抛出异常,*this 不会变为无值:值取决于候选项移动赋值的异常安全保证。
  • 否则(如果 rhs*this 持有不同的候选项),等效于 this->emplace<rhs.index()>(get<rhs.index()>(std::move(rhs)))。如果 T_i 的移动构造函数抛出异常,*this 变为 valueless_by_exception。

只有当 Types... 中的所有 T_i 都满足 std::is_move_constructible_v<T_i>std::is_move_assignable_v<T_i> 为真时,此重载才参与重载决议。如果 Types... 中的所有 T_i 都满足 std::is_trivially_move_constructible_v<T_i>std::is_trivially_move_assignable_v<T_i>std::is_trivially_destructible_v<T_i> 为真,则此重载是平凡的。


转换赋值。

  • 确定在相同范围内有每个 Types... 中的 T_i 的虚函数 F(T_i) 重载时,表达式 F(std::forward<T>(t)) 将通过重载决议选择的候选项类型 T_j,但以下情况除外:只有当声明 T_i x[] = { std::forward<T>(t) }; 对于某个虚构变量 x 有效时,才考虑重载 F(T_i)
  • 如果 *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> 为真,则等效于 this->emplace<j>(std::forward<T>(t))。如果在 emplace 内部的初始化中抛出异常,*this 可能会变为 valueless_by_exception。
  • 否则,等效于 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> 为真,并且 std::is_constructible_v<T_j, T> 为真,并且表达式 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 - 另一个变体 t - 一个可转换为变体候选项之一的值

返回值

*this

异常

  1. 可能会抛出由任何候选项的赋值和复制/移动初始化所抛出的任何异常。
  2. noexcept 规范
    noexcept(((std::is_nothrow_move_constructible_v<Types> && std::is_nothrow_move_assignable_v<Types>) && ...))
  3. noexcept 规范
    noexcept(std::is_nothrow_assignable_v<T_j&, T> && std::is_nothrow_constructible_v<T_j, T>)

示例

#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++ 标准。

DR应用于发布时的行为正确行为
LWG 3024C++17如果任何成员类型不可复制,则复制赋值运算符不参与重载决议改为定义为已删除
P0602R4C++17即使底层操作是平凡的,复制/移动赋值也可能不是平凡的要求传播平凡性
P0608R3C++17转换赋值盲目地组建重载集,导致意外的转换不考虑收缩和布尔转换
P2231R1C++20在 C++20 中,转换赋值在所需操作可为 constexpr 时不是 constexpr变为 constexpr
LWG 3585C++17转换赋值有时意外地格式错误,因为没有可用的移动赋值变为格式良好

std::variant<Types...>::operator=

// 1)
constexpr variant& operator=( const variant& rhs );
// 2)
constexpr variant& operator=( variant&& rhs ) noexcept(/* see below */);
// 3)
template< class T >
constexpr variant& operator=( T&& t ) noexcept(/* see below */);

将新值赋给现有变体对象。


复制赋值

  • 如果 *this 和 rhs 都因异常而无值,则不执行任何操作。
  • 否则,如果 rhs 无值,但 *this 有值,则销毁 *this 中包含的值并使其无值。
  • 否则,如果 rhs 持有与 *this 相同的候选项,则将 rhs 中包含的值赋给 *this 中包含的值。如果抛出异常,*this 不会变为无值:值取决于候选项复制赋值的异常安全保证。
  • 否则,如果 rhs 持有的候选项是 nothrow 可复制构造的或不是 nothrow 可移动构造的(分别由 std::is_nothrow_copy_constructiblestd::is_nothrow_move_constructible 确定),则等效于 this->emplace<rhs.index()>(get<rhs.index()>(rhs))。如果在 emplace 内部的复制构造中抛出异常,*this 可能会变为 valueless_by_exception
  • 否则,等效于 this->operator=(variant(rhs))

除非 Types... 中的所有 T_i 都满足 std::is_copy_constructible_v<T_i>std::is_copy_assignable_v<T_i> 为真,否则此重载被定义为已删除。如果 Types... 中的所有 T_i 都满足 std::is_trivially_copy_constructible_v<T_i>std::is_trivially_copy_assignable_v<T_i>std::is_trivially_destructible_v<T_i> 为真,则此重载是平凡的。


移动赋值

  • 如果 *thisrhs 都因异常而无值,则不执行任何操作。
  • 否则,如果 rhs 无值,但 *this 有值,则销毁 *this 中包含的值并使其无值。
  • 否则,如果 rhs 持有与 *this 相同的候选项,则将 std::get<j>(std::move(rhs)) 赋给 *this 中包含的值,其中 jindex()。如果抛出异常,*this 不会变为无值:值取决于候选项移动赋值的异常安全保证。
  • 否则(如果 rhs*this 持有不同的候选项),等效于 this->emplace<rhs.index()>(get<rhs.index()>(std::move(rhs)))。如果 T_i 的移动构造函数抛出异常,*this 变为 valueless_by_exception。

只有当 Types... 中的所有 T_i 都满足 std::is_move_constructible_v<T_i>std::is_move_assignable_v<T_i> 为真时,此重载才参与重载决议。如果 Types... 中的所有 T_i 都满足 std::is_trivially_move_constructible_v<T_i>std::is_trivially_move_assignable_v<T_i>std::is_trivially_destructible_v<T_i> 为真,则此重载是平凡的。


转换赋值。

  • 确定在相同范围内有每个 Types... 中的 T_i 的虚函数 F(T_i) 重载时,表达式 F(std::forward<T>(t)) 将通过重载决议选择的候选项类型 T_j,但以下情况除外:只有当声明 T_i x[] = { std::forward<T>(t) }; 对于某个虚构变量 x 有效时,才考虑重载 F(T_i)
  • 如果 *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> 为真,则等效于 this->emplace<j>(std::forward<T>(t))。如果在 emplace 内部的初始化中抛出异常,*this 可能会变为 valueless_by_exception。
  • 否则,等效于 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> 为真,并且 std::is_constructible_v<T_j, T> 为真,并且表达式 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 - 另一个变体 t - 一个可转换为变体候选项之一的值

返回值

*this

异常

  1. 可能会抛出由任何候选项的赋值和复制/移动初始化所抛出的任何异常。
  2. noexcept 规范
    noexcept(((std::is_nothrow_move_constructible_v<Types> && std::is_nothrow_move_assignable_v<Types>) && ...))
  3. noexcept 规范
    noexcept(std::is_nothrow_assignable_v<T_j&, T> && std::is_nothrow_constructible_v<T_j, T>)

示例

#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++ 标准。

DR应用于发布时的行为正确行为
LWG 3024C++17如果任何成员类型不可复制,则复制赋值运算符不参与重载决议改为定义为已删除
P0602R4C++17即使底层操作是平凡的,复制/移动赋值也可能不是平凡的要求传播平凡性
P0608R3C++17转换赋值盲目地组建重载集,导致意外的转换不考虑收缩和布尔转换
P2231R1C++20在 C++20 中,转换赋值在所需操作可为 constexpr 时不是 constexpr变为 constexpr
LWG 3585C++17转换赋值有时意外地格式错误,因为没有可用的移动赋值变为格式良好