DragonautX DragonautX - 1 month ago 15
C++ Question

Why does this C++ member initializer list not work?

In C++ in Plain English, Second Edition (1999), there's a section in the book that says an "=" can be used to do initialization while calling the right constructor. Like given a class

CStr
and a constructor
CStr(char*)
, the book says this:


Similarly, the final declaration calls the constructor CStr(char*):

CStr name3 = "Jane Doe";



I wanted to try the same thing with a
std::string
constructor. Part of my test code is this:

using namespace std;
class CStr {
private:
string name;
public:
CStr(string s): name(s) {}
};

int main() {
CStr name3 = "Jane Doe";
}


When I compile, however, I get an error in the name3 initialization:


"conversion from 'const char [9]' to non-scalar type 'CStr' requested".


Why didn't initializing CStr::name to a string s = "Jane Doe" work? An string test like
string nameTest{"Jane Doe"};
worked, so I thought this would work too. Maybe the book's old (it's the only book I have right now), but I'm thinking the error's more on my part.

Answer

Your book is old, but basically right[1]. Note that "Jane Doe" is not std::string, it's const char[9] (and could decay to const char*). So for CStr name3 = "Jane Doe";, two user-defined conversion is needed, (i.e. const char* -> std::string and std::string -> CStr), which is not allowed in one implicit conversion.

This also demonstrates that if CStr's construct takes const char* as its parameter, CStr name3 = "Jane Doe"; would work fine, because only one user-defined conversion is needed for it.

You could reduce one by adding explicit conversion:

CStr name3 = std::string("Jane Doe");

or use string literal (since C++14) directly, which is of type std::string:

CStr name3 = "Jane Doe"s;

Why didn't initializing CStr::name to a string s = "Jane Doe" work? An string test like string nameTest{"Jane Doe"}; worked, so I thought this would work too.

Your question is not clear enough, anyway, std::string nameTest{"Jane Doe"}; works because, (depends on your misunderstandings,) (1) only one implicit conversion (const char* -> std::string is needed here; (2) string nameTest{"Jane Doe"}; is direct initialization.

As @LightnessRacesinOrbit commented, direct initialization (i.e. CStr name3("Jane Doe") or CStr name3{"Jane Doe"} (since C++11) ) would work fine, while CStr name3 = "Jane Doe"; is copy initialization, they're different at some points:

In addition, the implicit conversion in copy-initialization must produce T directly from the initializer, while, 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

It means, for a copy initialization, the argument Jane Doe, which is a const char*, has to be converted to CStr directly; because two user-defined conversion is needed the code is rejected. For a direct initialization, it's fine to convert Jane Doe (const char*) to the argument of CStr's constructor, i.e. std::string firstly, then CStr::CStr(std::string) will be invoked to construct the object.


[1] "Jane Doe" is a c-style string literal which is const, from C++11 it's illegal to assign it to char*, e.g.

char * pc = "Jane Doe";         // illegal
const char * pcc = "Jane Doe";  // fine