14

The following code compiles with GCC 4.9.2 but not with Clang 3.5.0:

#include <string>

class Foo
{
public:
  explicit operator std::string() const;
};

std::string bar{Foo{}}; // Works in g++, fails in clang++
std::string baz(Foo{}); // Works in both

clang++ says:

foo.cpp:9:13: error: no matching constructor for initialization of 'std::string'
      (aka 'basic_string<char>')
std::string bar{Foo{}};
            ^  ~~~~~~~
...: note: candidate constructor not viable: no known conversion from 'Foo' to
      'const std::basic_string<char> &' for 1st argument
      basic_string(const basic_string& __str);
      ^

Curiously, it works if std::string is replaced with a primitive type like int.

Tavian Barnes
  • 12,477
  • 4
  • 45
  • 118

3 Answers3

4

This seems to be a Clang bug. [over.match.list]/1:

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

  • [..]
  • If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

Since the second line compiles fine, there is an inconsistency: They should be equivalent when it comes to overload resolution.

Columbo
  • 60,038
  • 8
  • 155
  • 203
3

From [class.conv.fct]/2:

A conversion function may be explicit (7.1.2), in which case it is only considered as a user-defined conversion for direct-initialization (8.5).

So the question is how you initialize your objects. Clearly baz is direct-initialized, so this works. By contrast, bar is direct-list-initialized, but not direct-initialized, and so the explicit conversion is not available.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

clang doesn't seem to care whether the conversion operator is explicit or not, and I believe it is correct due to the wording in [over.best.ics].

First of all, the direct-initialization

std::string baz(Foo{});

works on both gcc and clang, and is explained by [class.conv.fct]/2 as mentioned in KerrekSB's answer.

The direct-list-initialization

std::string bar{Foo{}};

on the other hand, does not consider any user defined conversions, explicit or not.

Quoting N3337, §13.3.3.1/4 [over.best.ics]

However, when considering the argument of a constructor or user-defined conversion function that is a candidate by 13.3.1.3 when invoked for the copying/moving of the temporary in the second step of a class copy-initialization, by 13.3.1.7 when passing the initializer list as a single argument or when the initializer list has exactly one element and a conversion to some class X or reference to (possibly cv-qualified) X is considered for the first parameter of a constructor of X, or by 13.3.1.4, 13.3.1.5, or 13.3.1.6 in all cases, only standard conversion sequences and ellipsis conversion sequences are considered.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
  • Is it possible that section talks about initializer lists specifically (i.e. `std::initializer_list`), and not braced init-lists in general? – Tavian Barnes Dec 19 '14 at 23:52
  • And I suppose if you're right then clang is wrong for accepting the code when `std::string` is replaced with `int`? – Tavian Barnes Dec 19 '14 at 23:53
  • @Tavian No, I'm pretty sure it means braced-init-lists because the 13.3.1.7 mentioned in there covers list-initialization in general. And I don't know the answer to your second question. The quoted paragraph mentioned `class X` all over, so maybe that means it only applies to user defined types? You should add the `int` observation to your clang bug report so you get an answer to both. – Praetorian Dec 20 '14 at 00:07