20

Given the following code:

#include <iostream>
#include <optional>

struct foo
{
    explicit operator std::optional<int>() {
        return std::optional<int>( 1 );
    }
    explicit operator int() {
        return 0;
    }
};

int main()
{
    foo my_foo;
    std::optional<int> my_opt( my_foo );
    std::cout << "value: " << my_opt.value() << std::endl;
}

gcc 7.2.0 writes value: 1.

MSVC 2017 (15.3) and clang 4.0.0 however write value: 0.

Which one is correct according to the C++ standard?

Tobias Hermann
  • 9,936
  • 6
  • 61
  • 134

1 Answers1

18

Since this is direct-initialization, we enumerate the constructors and just pick the best one. The relevant constructors for std::optional are :

constexpr optional( const optional& other ); // (2)
constexpr optional( optional&& other ) noexcept(/* see below */); // (3)

template < class U = value_type >
/* EXPLICIT */ constexpr optional( U&& value ); // (8), with U = foo&

Both are viable ((8) only participates in overload resolution if int is constructible from foo& and foo is neither std::in_place_t nor std::optional<int>, all of which hold), but (8) is an exact match whereas (2) and (3) require a user-defined conversion, so it should be preferred. gcc is wrong here.

However, gcc doesn't actually invoke (3) either. It just directly initializes my_opt from the result of converting my_foo to an optional<int>. This program with gcc 7.2 prints 3 but none of 1a, 1b, or 2:

#include <iostream>

template <class T>
struct opt {
    opt() { }
    opt(opt const& ) { std::cout << "1a\n"; }
    opt(opt&& ) { std::cout << "1b\n"; }

    template <class U>
    opt(U&& ) { std::cout << "2\n"; }
};

struct foo 
{
    explicit operator opt<int>() { std::cout << "3\n"; return {}; }
};

int main()
{
    opt<int> o(foo{});
}

I don't think that's an allowable route. I filed 81952.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • I always thought that overload resolution preferred non-templates. Why is it different here? – Rakete1111 Aug 23 '17 at 15:44
  • 6
    @Rakete1111 It's not different here. Overload resolution does not "always" prefer non-templates. Given two candidates that have equivalent conversion sequence rankings, one of the tiebreakers is then to prefer the non-template. But we don't have equivalent conversion sequence rankings here. – Barry Aug 23 '17 at 15:45
  • @Barry Don't we have two conversions with the same ranking? One to `int`, which would take (8) and one for `optional`, which would take (2)? – Rakete1111 Aug 23 '17 at 15:51
  • 1
    @Rakete1111 No, there's no conversion for `(8)`. The conversion to `int` happens inside the constructor itself. – Barry Aug 23 '17 at 15:52
  • This is very likely to be another bug with GCC's P0135 implementation. – T.C. Aug 23 '17 at 16:06
  • @T.C. Filed a bug. It's odd, since I think that's probably what we want to happen - even if it's not allowed to happen. – Barry Aug 23 '17 at 16:17
  • @T.C. what is the GCC P0135 implementation? – TheCppZoo Aug 23 '17 at 16:33
  • @Downvoter Reasoning? I'm fairly sure this is correct, and the gcc bug just got moved to NEW status. – Barry Aug 24 '17 at 11:45
  • @Barry Sorry, I guess I do not understand your answer fully yet. Is clang correct with its output (`0`) according to the standard? I would expect something different. But I guess it would not really fit into this thread, so I just opened [a new question](https://stackoverflow.com/questions/45861479/why-is-a-cast-operator-to-stdoptional-ignored). – Tobias Hermann Aug 24 '17 at 12:21