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

std::variant behaves differently in MSVC and gcc

Update:这是一个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

  1. Converting constructor. Constructs a variant holding the alternative type T_j that would be selected by overload resolution for the expression F(std::forward<T>(t)) if there was an overload of imaginary function F(T_i) for every T_i from Types... in scope at the same time, except that: An overload F(T_i) is only considered if the declaration T_i x[] = { std::forward<T>(t) }; is valid for some invented variable x; Direct-initializes the contained value as if by direct non-list-initialization from std::forward<T>(t).

并且问题通过重载解析转换为 F(long long)F(double) 的哪个函数被选择为agianst 参数 1

intlong long是整数转换(假设sizeof(long long)大于sizeof(int)),intdouble是浮点积分转换,两者的排名都不高。所以调用有歧义,程序格式错误。

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不合规,还是我的理解有误?

在引用的规则中,仅当候选类型的 copy-list-initialization 从参数类型开始工作时才考虑重载。此检查不(不能)考虑参数的常量表达式状态,因此 int 到任何浮点类型是 narrowing 转换并且被禁止列表初始化(尽管在典型的实现中 double 可以准确地表示 int 的每个值)。因此,GCC(、libstdc++)正确忽略double替代方案。