为什么将 int 分配给 std::variant<long int, ...> 会失败?

Why does assigning an int to a std::variant<long int, ...> fail?

我觉得我在分配给变体时遗漏了一些关于 int 类型提升的明显信息。

在gcc version 9.3.0 (Ubuntu 9.3.0-11ubuntu0~18.04.1)上,使用-std=c++17编译,以下代码编译失败:

#include <variant>
#include <iostream>

int main()
    {
    std::variant<long int, bool> v;  // works fine if "long" is omitted
    long int sanity = 1;             // verify that we can assign 1 to a long int; works fine

    std::cout << sizeof(sanity) << "\n";

    v = 1;                           // compiler error here: why doesn't this assign to the long int variant of v?

    return 0;
    }

错误信息:

error: no match for ‘operator=’ (operand types are ‘std::variant<long int, bool>’ and ‘int’)

有没有什么神奇的方法可以让这项工作按预期进行,而不需要在作业中进行显式转换?谢谢!

分配给变体并不是简单地分配给变体中当前活动的类型。相反,右侧的类型用于确定哪些可能类型(备选方案)最适合右侧。然后,将该类型分配给。

因此,v = 1; 不会自动分配给已经在 v 中的 long int 值,而是进行编译时计算以确定是否 long intbool 更适合 int (右侧的类型)。使用重载解析规则确定最佳匹配。换句话说,我们必须假设存在两个函数

void f(long int);
void f(bool);

然后问自己 f(1) 会调用哪个函数。如果选择 long int 重载,则 v = 1 分配给 long int 对象。如果选择 bool 重载,则当前在 v 中的 long int 将被销毁,并构造一个值为 1 的新 bool 对象。

不幸的是,此重载解析不明确:1 需要“积分转换”以匹配 long intbool。因此,将 1 分配给 v 是一个编译时错误。如果备选方案是 intbool,那么 int 备选方案将产生完全匹配并且不会有歧义。

此特定示例已在 C++20 中修复:bool 替代方案已从考虑中移除,因为需要缩小转换才能从 int 初始化 bool 值价值。因此,在 C++20 中,此代码将始终分配给 long int 替代项(如果变体中当前有一个 bool 对象,它将被销毁)。

从 int 到这两种类型的转换具有相同的“距离”。

它不知道你要分配给哪个。

您可以制作一个拒绝从 int 转换的非 bool 布尔值。

struct boolean{
  bool value=false;
  constexpr boolean()=default;
  template<class T, std::enable_if_t<std::is_integral<T>{}, bool> =true>
  boolean(T)=delete;
  constexpr boolean(bool b):value(b){}
  constexpr boolean(boolean const&)=default;
  constexpr boolean& operator=(boolean const&)=default;
  constexpr explicit operator bool()const{return value;}
  constexpr friend bool operator==(boolean lhs, boolean rhs) { return lhs.value==rhs.value; }
  constexpr friend bool operator!=(boolean lhs, boolean rhs) { return lhs.value!=rhs.value; }
};

可能还有一些操作。

那么 variant<boolean, long int> 不会从 int 转换。

Live example.