为什么将int赋值给std::variant<long int, ...>失败?

3

我觉得在给变量赋值时,整型类型提升方面有一些很容易被忽略的细节。

使用gcc版本为9.3.0(Ubuntu 9.3.0-11ubuntu0~18.04.1)并使用-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’)

有没有什么魔法可以让这个工作按预期进行,而不需要在赋值时进行显式转换?谢谢!

2个回答

7

给变体赋值并不仅仅是将值分配给变体中当前正在使用的类型。相反,右侧值的类型用于确定哪种可能类型(替代选项)最适合右侧值。然后,将该类型分配给变量。

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

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

我们需要询问自己,f(1) 会调用哪个函数。如果选择了 long int 的重载,那么将 1 赋值给 long int 对象。如果选择了 bool 的重载,则当前在 v 中的 long int 将被销毁,并构造一个新的 bool 对象并赋值为 1。

不幸的是,这种重载决议是有歧义的:1 需要进行 "整数转换" 才能匹配到 long int 或者 bool。因此,将 1 赋值给 v 是一个编译时错误。如果备选项为 intbool,则 int 备选项将产生精确匹配,不会有歧义。

在 C++20 中,这个特定的例子已经被修复:由于需要对从 int 值初始化 bool 值进行缩小转换,因此从备选项中移除了 bool。因此,在 C++20 中,这段代码将始终将值分配给 long int 备选项(如果当前在变体中存在 bool 对象,则其将被销毁)。


3

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

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

您可以创建一个非布尔布尔值,拒绝从int进行转换。

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 进行转换。

现场示例


感谢提供的解决方法...不幸的是,据我所知,“requires”是C++20的扩展,而我的当前项目中并没有这个扩展...即使有,根据Brian的回复,也不需要这个解决方法。 - Joe Schober
1
@JoeSchober需要可以被SFINAE替代,如果你能够接受的话。编辑完成。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接