在MSVC和gcc中,std::variant的行为不同。

15

更新: 这是C++标准的缺陷,在C++20 (P0608R3)中已得到修复。此外,VS 2019 16.10通过使用/std:c++20修复了这个错误。

MSVC 19.28拒绝以下代码,但gcc 10.2接受它并输出true false

#include <iostream>
#include <variant>

int main()
{
    std::variant<long long, double> v{ 0 };
    std::cout << std::boolalpha << std::holds_alternative<long long>(v) << ' ' << std::holds_alternative<double>(v) << std::endl;
}

根据cppreference:
转换构造函数。构造一个变量,其中包含选择类型T_j的替代方案,该类型将为表达式F(std::forward<T>(t))选择重载分辨率,如果在同一时间范围内作用域中存在每个T_i的虚构函数F(T_i)的重载,则仅考虑重载F(T_i),除非对于某些虚构变量x,声明T_i x[] = { std::forward<T>(t) };有效;以直接非列表初始化方式初始化所包含的值,如同通过std::forward<T>(t)进行直接非列表初始化。
问题是将int转换为long long是整数转换(假设sizeof(long long)大于sizeof(int)),将int转换为double是浮点整数转换,两者都不高于另一个。 因此,调用是模棱两可的,程序是有缺陷的。
MSVC按我预期拒绝了该代码,但令我惊讶的是,gcc接受了它。 此外,cppreference上还有一个类似的例子:
std::variant<std::string> v("abc"); // OK
std::variant<std::string, std::string> w("abc"); // ill-formed
std::variant<std::string, const char*> x("abc"); // OK, chooses const char*
std::variant<std::string, bool> y("abc"); // OK, chooses string; bool is not a candidate
/* THIS ONE -> */ std::variant<float, long, double> z = 0; // OK, holds long
                                         // float and double are not candidates 

所以我的问题是:gcc或MSVC不符合标准,还是我的理解有误?

“假设 sizeof(long long) 大于 sizeof(int)”,你在两个配置中都检查过了吗? - Jarod42
就此而言,clang 似乎也接受它 - Patrick Roberts
@Jarod42 我非常确定,在我的系统上(Windows 10)。 - El Mismo Sol
1个回答

5
在引用规则中,只有当候选类型可以从参数类型进行复制列表初始化时才考虑重载。这个检查不会(也不能)考虑参数的常量表达式状态,所以将int转换为任何浮点类型都是一种缩窄转换,并且在列表初始化中被禁止(尽管在典型实现中double可以精确表示int的每个值)。因此,GCC(即libstdc++)正确地忽略了double备选方案。

标准措辞是“T_i x[] = { std::forward<T>(t) };”,它考虑了参数的值。所以我担心你是错的。 - El Mismo Sol
尽管列表初始化禁止缩小转换,但“std :: forward”有效地执行显式转换。例如,“float f1 [] = {std :: forward <float>(1)};”和“float f2 [] = {std :: forward <float>(0xffffffffffffffffuLL)};”都是有效的。 - El Mismo Sol
@方圆圆:被检查的是float f[]={std::forward<int>(t)};,其中T是参数类型,而不是虚构参数(即备选)类型。同样,t是参数,而不是参数,因此它永远不是常量表达式;检测参数是否为常量表达式是无法实现的。 - Davis Herring

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