std::variant behaves differently in MSVC and gcc
Asked Answered
C

1

15

Update: This is a C++ standard defect, which is fixed in C++20 (P0608R3). Also, VS 2019 16.10 has fixed this bug with /std:c++20.

MSVC 19.28 rejects the following code but gcc 10.2 accepts it and outputs 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;
}

According to 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).

And the question is converted to which function of F(long long) and F(double) is selected agianst argument 1 by overload resolution.

Converting int to long long is an integral conversion (supposing sizeof(long long) is bigger than sizeof(int)) and converting int to double is an floating-integral conversion, neither ranks higher that the other. So the call is ambiguous and the program is ill-formed.

MSVC does rejected the code as I expected but to my surprise, gcc accepts it. Besides, there is also a similar example on 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 

So my question is: is gcc or MSVC non-conformance, or my understanding is wrong?

Condescension answered 1/1, 2021 at 14:19 Comment(3)
"supposing sizeof(long long) is bigger than sizeof(int)", Have you checked that in both configuration?Narceine
FWIW, clang seems to accept it as well.Nel
@Narceine I'm pretty sure.on my system (Windows 10).Condescension
M
5

In the quoted rule, an overload is considered only if copy-list-initialization for the candidate type works from the argument type. This check doesn’t (can’t) consider the constant-expression status of the argument, so int to any floating-point type is a narrowing conversion and is disallowed by list-initialization (despite the fact that on typical implementations double can exactly represent every value of int). GCC (i.e., libstdc++) is therefore correct to disregard the double alternative.

Montage answered 1/1, 2021 at 17:4 Comment(3)
The stadard wording is T_i x[] = { std::forward<T>(t) }; which considers the value of the argument. So I'm afraid you are wrong.Condescension
Although list initialization prohibits narrowing conversion, but std::forward effectively performs an explicit conversion. For example, both float f1[] = { std::forward<float>(1) }; and float f2[] = { std::forward<float>(0xffffffffffffffffuLL) }; are valid.Condescension
@方圆圆: It’s float f[]={std::forward<int>(t)}; that is checked: T is the argument type, not the fictitious parameter (i.e., alternative) type. Similarly, t is the parameter, not the argument, so it’s never a constant expression; detecting that the argument is a constant expression is unimplementable.Montage

© 2022 - 2024 — McMap. All rights reserved.