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?

El Mismo Sol
  • 869
  • 1
  • 6
  • 14

1 Answers1

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.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • The stadard wording is ``T_i x[] = { std::forward(t) };`` which considers the value of the argument. So I'm afraid you are wrong. – El Mismo Sol Jan 02 '21 at 00:38
  • Although list initialization prohibits narrowing conversion, but ``std::forward`` effectively performs an explicit conversion. For example, both ``float f1[] = { std::forward(1) };`` and ``float f2[] = { std::forward(0xffffffffffffffffuLL) };`` are valid. – El Mismo Sol Jan 02 '21 at 01:42
  • @方圆圆: It’s `float f[]={std::forward(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. – Davis Herring Jan 02 '21 at 06:03