4

These work:

struct WithString {
  WithString(std::string){};
};

void takeString(std::string){}

//implicit conversions:
takeString("hello");
WithString("hello");

But this does not:

WithString makeWithString() { return "hello";}

// error: no viable conversion from returned value of type 'const char [6]'...

If "hello" is implicitly converted to std::string in the first two cases, why cannot it not be in the last case? Note that I did not specify the WithString constructor as explicit, so I'd expect such a conversion.

I can get the behavior to work by doing this:

struct WithString {
  WithString(std::string){};
  WithString(const char *){};
};

I'm just curious about this oddity. If I postulate a guess, I would say it is because in the first two working cases, the conversion is between const char * to std::string, but in the error case, this would instead require a chain of 2 conversion, first from const char * to std::string, and then from std::string to WithString. So perhaps that is the reason, but I'm not sure.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
johnbakers
  • 24,158
  • 24
  • 130
  • 258

3 Answers3

6

I would say it is because in the first two working cases, the conversion is between const char * to std::string, but in the error case, this would instead require a chain of 2 conversion, first from const char * to std::string, and then from std::string to WithString. So perhaps that is the reason, but I'm not sure.

Exactly.

Without your const char* constructor overload, this:

WithString makeWithString() { return "hello";}

would require two user-defined implicit conversions; one to std::string and another to WithString. That is not possible.

Here, though, there's only one implicit conversion (to std::string):

takeString("hello");

And the same is true here, because the subsequent "conversion" to WithString is explicit:

WithString("hello");

I can get the behavior to work by doing this:

struct WithString {
  WithString(std::string){};
  WithString(const char *){};
};

Yes, that's what you should do.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
3

Your method:

WithString makeWithString() { return "hello";}

needs two conversions: The implicit const char *-to-std::string conversion, then a construction of a WithString object. C++ allows at most one of these to happen implicitly. See also the discussion here:

Non-const copy constructor and implicit conversions on return value

einpoklum
  • 118,144
  • 57
  • 340
  • 684
-1

Read implicit conversions section in the C++ standard. I tried the following code in VS 2015 and it compiled with no errors.

#include <string>

struct WithString {
    WithString(std::string) {};
};

void takeString(std::string) {}

//implicit conversions:
void someFunc()
{
    takeString("hello");
    WithString("hello");
    WithString t = "hello";
}

WithString makeWithString() { return "hello"; }

It appears that VS2015 is incorrect (treating a conversion from const char* to string as a standard conversion). The following code should work according to the standard, but produces an error in VS2015:

WithString makeWithString() { return "hello"s; }
WithString t = "hello"s;

See also copy initialization. In the Notes it explicitly calls WithString t = "hello"; an error.

  • If MSVS 2015 allows this, it is buggy. I'm curious what C++11, C++14, C++17 features you think should permit this program? – Lightness Races in Orbit Jan 15 '17 at 15:25
  • Indeed MSVS 2015 C++ allows this. I guess the question is whether const char* to std:string is a _standard conversion_ or not. If it is, then it is correct; if not (i.e. user-defined conversion), then it is not. Hovering over the "hello" in Visual Studio for all cases yields std::string::basic_string(const char*_Ptr). – vdovydaitis3 Jan 15 '17 at 15:46
  • And, yes I find the additional note under [copy initialization](http://en.cppreference.com/w/cpp/language/copy_initialization): the implicit conversion in copy-initialization must produce T directly from the initializer, e.g. direct-initialization expects an implicit conversion from the initializer to an argument of T's constructor. struct S { S(std::string) {} }; // implicitly convertible from std::string S s("abc"); // OK: conversion from const char[4] to std::string S s = "abc"; // Error: no conversion from const char[4] to S S s = "abc"s; // OK: conversion from std::string to S – vdovydaitis3 Jan 15 '17 at 15:59
  • 12.3/4: At most one user-defined conversion (constructor or conversion function) is implicitly applied to a single value. – Lightness Races in Orbit Jan 15 '17 at 23:48