4

I have the following code:

#include <string_view>

class Foo
{
public:
    Foo(std::string_view) {}
};

When I do this, everything compiles fine (using clang v8, C++17):

Foo f("testing");

However, if I use copy initialization it fails:

Foo f = "testing";

Diagnostic:

prog.cc:15:9: error: no viable conversion from 'const char [8]' to 'Foo'
    Foo f = "testing";
        ^   ~~~~~~~~~
prog.cc:7:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'const char [8]' to 'const Foo &' for 1st argument
class Foo
      ^
prog.cc:7:7: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'const char [8]' to 'Foo &&' for 1st argument
class Foo
      ^
prog.cc:10:5: note: candidate constructor not viable: no known conversion from 'const char [8]' to 'std::string_view' (aka 'basic_string_view<char>') for 1st argument
    Foo(std::string_view) {}
    ^
1 error generated.

Looking at the constructors for string_view, I don't see any overloads that take char const[], is that maybe the issue? I realize I could use "testing"sv to solve this but I feel like the string literal case should work as well.

Why doesn't the copy initialization case work? How can I make it work with string literals?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • 2
    `Foo f = {"testing"};` works. Initialization is such a mess in C++ with a dozen types of initializations that really should just be one imho. Q: you have a generic code. What do you use: `T a = init;` , `T a(init)` , `T a{init}` , `T a = {init}` , `T a = T(init)` , `T a = T{initi}` , `auto a = T(init)` , `auto a = T{init}` , `auto a = (T) init`? Not even the experts have a answer to this (well, they do narrow it down to 2 or 3 choices, but still ... ) – bolov Jul 24 '19 at 16:08
  • 2
    @bolov https://i.imgur.com/3wlxtI0.gifv – void.pointer Jul 24 '19 at 16:19

1 Answers1

8

Foo f = "testing"; is copy initialization, which asks for implicit conversion from "testing" (which is of type const char[8]) to Foo. "testing" could decay to const char*, and then still two user-defined conversions are required. The conversion from const char* to std::string_view, and one from std::string_view to Foo. But only one user-defined conversion is allowed in one implicit conversion sequence.

Foo f("testing"); is direct initialization, which behaves differently. "testing" decays to const char* and then converts to std::string_view, which is used as the argument of Foo's constructor to initialize f directly.

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.

Implicit conversion is defined in terms of copy-initialization: if an object of type T can be copy-initialized with expression E, then E is implicitly convertible to T.

As the workaround, if you want to stick to copy-initialization, you can reduce the required user-defined conversion. As you showed it's a good idea to apply Foo f = "testing"sv;, or Foo f = std::string_view("testing");, which has the same effect.

Community
  • 1
  • 1
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • I'm sighing at myself because now that I've read your answer it made me realize I already knew about this but forgot it. My brain wants to ignore the complexities and focus on what feels intuitive. But we can't have what's intuitive due to tons of technicalities. It's frustrating. Thank you for your answer and reminder about this. – void.pointer Jul 24 '19 at 16:51