24

I read that C++ compilers are able to implicitly convert types, when fitting converting constructors or operands are provided. I actually found example code that looks much like this:

class Dog{
    private:
       string name;
    public:
        Dog(string n):name(n){} //This as the converting constructor
}

int main(){
    Dog d = "rover";
}

Whenever I run this code the compiler throws an error message:

conversion from ‘const char [6]’ to non-scalar type ‘Dog’ requested Dog d = "rover";

When compiling I add the compiler option -std=c++11, so it shouldn't be about the C++ version, right?
Examples I found on the internet (at least to me) look quite identical, so I have no clue of what is going wrong here.
My input about this topic comes for example from this video: Convert constructor and overloading operators - thenew moston

Vincent Savard
  • 34,979
  • 10
  • 68
  • 73
Julian
  • 525
  • 5
  • 19
  • I totally agree - but since I didn't knew the term _converting constructor_ before, I couldn't find anything about _implicit type conversion c++_. All I ever found was _integer promotion_, so I gave youtube a shot. And it actually helped me better. So instead of posting your opinion - post facts please. Its usually more complicated than a first glance view. – Julian Jun 07 '16 at 07:36
  • 8
    It was not a criticism on your question, or your link. Rather a random thought on why people would bother to make a tutorial *video*, with all kinds of distractions (voice, pacing, environment of choice), instead of a text-based tutorial (which also can be easily edited and updated to include additional information). Just a thought that sprung up when I followed the link, saw "video", and immediately closed the tab because I couldn't be bothered to sit through it all. Ignore me. ;-) – DevSolar Jun 07 '16 at 07:42
  • @DevSolar Because in videos you see people having the same errors and problems you have. They get stuck and then solve it somehow, and from that you learn the way to solve these types of problems. If you would read an article instead, all the parts of getting stuck and finding a solution would be lost, you just get the final result. – nwp Jun 07 '16 at 08:31
  • And why are you passing the string parameter by value? If this is meant to use the new *sinking* technique, you are missing `std::move`. – JDługosz Jun 07 '16 at 08:31
  • No I'm just rather new to c++ and therefore forget to do this frequently. I actually have never even heard of any sinking technique, but I will make sure to check it out - always grateful for new input :) – Julian Jun 07 '16 at 08:34
  • 1
    @DevSolar: You think that's weird -- last week, some guy who had asked a question on Stack Overflow and got requests for clarification *responded by making a video*! – Kerrek SB Jun 07 '16 at 08:38
  • 4
    @nwp in a well written tutorial you can get that too, and it may be easier to follow than it would be in a video. A good teacher should be able to communicate as easily with text as with a video lecture – Chris Beck Jun 07 '16 at 08:46
  • 1
    Sinking: For videos, see the last few years of the *Going Native* conference, explaining fresh new techniques for newer compilers. This includes a great deal due to *move semantics* available as of C++11. – JDługosz Jun 07 '16 at 08:58
  • That code works nicely for me. – rain_ Jun 07 '16 at 10:20
  • 1
    seriously? Considering only one implicit conversion is to be used at once, this would discredit all the given answers?! Once I thought I understood it..... – Julian Jun 07 '16 at 10:23
  • 1
    I swear! When in Visual Studio 2015, ive got no errors. If I use godbolt.org i get the same error as you get. Maybe Microsoft compiler implicity converts const char* to std::string – rain_ Jun 07 '16 at 11:01

3 Answers3

37

You also need to understand that you are using copy initialization rather than direct initialization.

They are different, and you need to understand how, and their relationship with explicit. You need to understand how chains of conversions work, with at most one user-defined conversion involved.

Dog d1 ("rover");
Dog d2 = "rover";

The d2 case tries to convert the literal to a Dog, and then copy (move) that to d2. But that would be a double conversion: const char* to string and then string to Dog.

The d1 case constructs d1 passing the argument to the constructor, so only one conversion const char* to string. (In both cases, promoting const char [6] to const char* is in there too but doesn't count toward the "only one" allowed, being in a different category.)

The copy-initialization does not specify "rover" as an argument of the constructor. It says "here is something, but a Dog is needed here". here is the right-hand-side of the copy-init declaration syntax, not any identifiable function. The compiler than has to find a legal conversion.

In the direct-init case, you are simply giving parameters for a function (a constructor). The compiler converts what you gave it into the declared argument type.

Community
  • 1
  • 1
JDługosz
  • 5,592
  • 3
  • 24
  • 45
  • 3
    This, really, should be accepted as the full answer. – lapk Jun 07 '16 at 08:49
  • The other answers also explained the non-transitive conversion flow. However I need to first understand the whole _copy initialization_ thing first, to recognize your answer as the superior. As soon as I am behind all this I come back and consider changing the accepted mark. – Julian Jun 07 '16 at 09:54
  • Ok I guess, I got it. Your answer actually requires some more research, for new-bs like me, but then again thats not bad at all. I'm gonna mark your answer as accepted. But if you don't mind I think including some seperation like: 1. easy solution: - Dog(const char* name):name(name){} 2. specific explanation: - your stuff would be great for both quick and specific research. Nevertheless, great answer. – Julian Jun 07 '16 at 10:37
  • 2
    This is a very well organized explanation, kudos – Chris Beck Jun 07 '16 at 15:36
  • 1
    Quick: I believe d1 works with the class as you wrote it. Adding a char* constructor would not be nice. – JDługosz Jun 07 '16 at 15:39
  • I'm amazed that the other answers focused on that, rather than direct vs copy init and the real issue which is that what you wrote is not a call to the function, but some indirection away from that. – JDługosz Jun 07 '16 at 15:47
  • Hi @JDługosz, would you mind helping me out with this once more? I was interested in seeing what you explained first hand. So I wrote another short program defining a Dog class and giving it standard-, explicit direct-, converting- and copy constructor. I used cout to detect, which constructor was called when. When I create a Dog like this: Dog d = "rover"; The only thing I see is _"converting constructor called"_ When I actually assumed there would be first a call to the converting- and then to the copy constructor?! – Julian Jun 10 '16 at 07:25
  • 1
    The compiler is allowed to optimize out the copy! Even so, it must check for availability and ability to call it. Make the copy ctor private and you'll get an error instead. – JDługosz Jun 10 '16 at 14:53
  • Why not pick up Stroustrup's book? – JDługosz Jun 10 '16 at 14:55
26

Note "rover" is not std::string, it's const char[6] (with the null character at the end) (might decay to const char*), to make Dog d = "rover"; work, "rover" needs to be converted to std::string and then converted to Dog.

But user-defined conversion won't be considered twice in one implicit conversion.

(emphasis mine)

User-defined conversion function is invoked on the second stage of the implicit conversion, which consists of zero or one converting constructor or zero or one user-defined conversion function.

You can convert "rover" to std::string explicitly to make it work.

Dog d = std::string("rover");
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
17

You need another constructor that allows you to construct from a const char*:

Dog(const char* n):name(n){}

Remember that "rover" isn't a std::string, and the type won't be deduced to use the conversion constructor implicitly. As @songyuanyao mentioned in their answer the conversion will be done once only.

Another option is to write:

Dog d = std::string("rover");
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
  • 6
    This answer would be even better, though, if it also explained why the conversion from `const char*` to `std::string` cannot be used in the OP's context. – Angew is no longer proud of SO Jun 07 '16 at 07:25
  • 1
    But `"rover"` isn't a `const char*` either. So why would this fix the problem? Or why is there a problem in the first place? – juanchopanza Jun 07 '16 at 07:25
  • I see my problem is not a lack of understanding how to use the converting constructor, but in how strings or char pointers are handled differently. – Julian Jun 07 '16 at 07:30
  • @juanchopanza _"So why can't we initialize a `Dog` from `"rover"`?"_ Because the deduction takes only one level at the initialization? – πάντα ῥεῖ Jun 07 '16 at 07:38
  • 1
    Another option would be to use `"rover"s` – tkausl Jun 07 '16 at 10:30
  • 1
    Adding that consructor is not nice, as the class needs a string anyway. You don't want every single class that takes string arguments to have another form, especially if there's more than one string argument! You **can** initialize Dog from a string literal, *using direct initialization*. I think this answer is not "even better" but misses the point. – JDługosz Jun 07 '16 at 15:42