1

My class is like:

class X {

public:
        :
        :
        :
  operator const char*() const { return "foo"; };
  operator std::string() const { return std::string( "foo" ); };
        :
        :
        :
};

My hope was to be able to initialize a std::string implicitly, like this, but huge wall of errors:

string s1( x );

Even explicitly doesn't work:

string s1( (string) x );

However casting x to (const char*) works fine:

string s1( (const char*) x );

In addition to whatever solution you guys have, any other recommendations for making a type that should be as freely-convertible to and from C-style strings and std:string? (I already have constructors for X taking const char* and std::string as well as an x, and can take assignments from those types.

Swiss Frank
  • 1,985
  • 15
  • 33
  • 2
    Code works just fine [here](http://coliru.stacked-crooked.com/a/d291dc0ec1dda291). Can we get a [mre], also include the **exact** error message(s). – NathanOliver Mar 30 '20 at 12:26
  • If even a [mre] gives an enormous number of errors, usually the first few are the most relevant. – aschepler Mar 30 '20 at 12:27
  • @NathanOliver It works with C++17, but not with C++11/14. – Daniel Langr Mar 30 '20 at 12:32
  • 2
    @DanielLangr Hence me asking for a MRE. I can't know what they are using if they don't tell me. For all I know it is complaining about the lack of `std::` in front from `string x` – NathanOliver Mar 30 '20 at 12:34
  • 1
    [Here](https://godbolt.org/z/MCpXee) is a demo with custom classes that generates the same problem. For some reason, in C++17, copy/move constructor has higher precedence, which avoids overload ambiguity. – Daniel Langr Mar 30 '20 at 12:41
  • 1
    To my previous comment, it seems that copy/move constructor does not have higher precedence, but is elided due to _guaranteed copy elision_ in C++17. Then, there are less conversion steps and, therefore, no ambiguity. – Daniel Langr Mar 31 '20 at 07:32

1 Answers1

0

I am not sure my explanation will be 100% precise, but if not, hopefully, someone will clarify it. Instead of std::string and char*, I will use a custom Y class and int for the sake of simplicity:

struct Y 
{
   Y(int) { }
};

struct X
{
   operator int() const { return 1; }
   operator Y() const { return Y(0); }
};

int main()
{
   X x;
   Y y(x);
}

With this code, in C++11/14, the compiler has two options how to proceed:

  1. It converts x to Y(0) by X::operator Y() and then passes this Y(0) to the move constructor of Y.

  2. It converts x to int(1) by X::operator int() and then passes this int(1) to the converting constructor Y::Y(int).

Both options are equivalent and, therefore, generate ambiguity, which results in a compilation error.


In C++17, the situation is different due to guaranteed copy elision. Here, the call of move constructor in (1.) is elided, and, consequently, the first conversion sequence is selected, since it requires only 1 conversion instead of 2.


Anyway, this version:

Y y(static_cast<Y>(x));

also generates ambiguity in C++11/14, which, I must admit, I do not understand.

Daniel Langr
  • 22,196
  • 3
  • 50
  • 93
  • 1
    The ambiguity for the static_cast is because in order to execute the cast, you must construct an instance of Y from an instance of X. There are two ways to do this - the instance of X can be implicitly converted to a Y - or the instance of X can be implicitly converted to an int, which can be used to construct an instance of Y. If you mark the conversion operators explicit, then the casts will work as expected - users will get what they expect - implicit conversions can be a form of evil so their use must be carefully designed. – Jody Hagins Mar 30 '20 at 13:36