Constructing a std::string_view from a braced std::string, clang and gcc disagree with -Wconversion

Viewed 1006

GCC 7.2 and clang 5.0 seem to disagree about the following code when compiled with -std=c++17 -Werror -Wconversion:

#include <iostream>
#include <string>
#include <string_view>

int main(int argc, char* argv[]) {
    std::string s = "Hello, World";
    std::string_view vs{s};
    std::cout << std::string{vs} << '\n';
    return EXIT_SUCCESS;
}

Compiled with clang++ -std=c++17 -Wall -Werror -Wconversion, it compiles correctly (see https://godbolt.org/g/JZn8cL)

However, GCC 7.2 issues a somewhat perplexing diagnostic (see https://godbolt.org/g/kmYbJ3):

<source>: In function 'int main(int, char**)':
7 : <source>:7:26: error: choosing 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__sv_type() const [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__sv_type = std::basic_string_view<char>]' over 'constexpr std::basic_string_view<_CharT, _Traits>::basic_string_view(const std::basic_string_view<_CharT, _Traits>&) [with _CharT = char; _Traits = std::char_traits<char>]' [-Werror=conversion]
     std::string_view vs{s};
                          ^
7 : <source>:7:26: error:   for conversion from 'std::__cxx11::string {aka std::__cxx11::basic_string<char>}' to 'std::basic_string_view<char>' [-Werror=conversion]
7 : <source>:7:26: note:   because conversion sequence for the argument is better
cc1plus: all warnings being treated as errors
Compiler exited with result code 1

Both compilers compile the code fine after changing vs{s} to vs(s).

Which compiler is right here? Is brace initialization of a std::string_view from a std::string running afoul of conversion rules somehow, and clang is failing to issue a diagnostic when it could/should? Or is GCC in the wrong and the diagnostic is erroneous?

2 Answers

Which compiler is right here?

Both? Compilers are allowed to give warnings, or not, as they choose. It's just that when you write:

std::string_view vs{s};

You might be under the impression that this is doing something like aggregate initialization or at least invoking a string_view constructor, but it's not - and maybe that impression isn't as likely to happen if you wrote:

std::string_view vs(s);

so gcc gives you a warning (since you asked for one).

The code is fine. Both compilers are fine. Just use ()s to initialize, there's no reason to use {} here (there is no difference in behavior in the two approaches).

Your original question ("is this invalid C++?") has now been answered ("no."), but it's clear from the comments you're now interested in why gcc warns about this valid code. That is a more interesting question.

Removing the template arguments, the warning reduces to this:

warning: choosing 'std::string::operator std::string_view()' over 'constexpr std::string_view(const std::string_view&) ' [-Wconversion]

So it's warning about a user-defined conversion operator being chosen over a constructor... but that constructor couldn't actually be used for conversion. Here is another example that warns for the same reason:

class A {  // Like string_view
public:
    A() {}
};
class B {  // Like string
public:
    operator A() {return A();}
};

int main() {
    B b;
    A a{b};
}

This gives:

<source>: In function 'int main()':
12 : <source>:12:10: warning: choosing 'B::operator A()' over 'constexpr A::A(A&&)' [-Wconversion]
     A a{b};
          ^
12 : <source>:12:10: warning:   for conversion from 'B' to 'A' [-Wconversion]
12 : <source>:12:10: note:   because conversion sequence for the argument is better
12 : <source>:12:10: warning: choosing 'B::operator A()' over 'constexpr A::A(const A&)' [-Wconversion]
12 : <source>:12:10: warning:   for conversion from 'B' to 'A' [-Wconversion]
12 : <source>:12:10: note:   because conversion sequence for the argument is better

By the way, adding e.g. A(int) constructor does not add anything to the warning, so it is focused on the move constructor and copy constructor of A. Deleting the copy and move constructors with =delete does not change the warning either.

This clarifies what is causing the warning, but not why it warns. The gcc documentation for -Wconversion says:

Warn for implicit conversions that may alter a value. This includes conversions between real and integer, like abs (x) when x is double; conversions between signed and unsigned, like unsigned ui = -1; and conversions to smaller types, like sqrtf (M_PI). Do not warn for explicit casts like abs ((int) x) and ui = (unsigned) -1, or if the value is not changed by the conversion like in abs (2.0). Warnings about conversions between signed and unsigned integers can be disabled by using -Wno-sign-conversion.

For C++, also warn for confusing overload resolution for user-defined conversions; and conversions that never use a type conversion operator: conversions to void, the same type, a base class or a reference to them. Warnings about conversions between signed and unsigned integers are disabled by default in C++ unless -Wsign-conversion is explicitly enabled.

So it depends on what you consider to be "confusing overload resolution for user-defined conversions". Perhaps the gcc devs consider A a{b} to look like a call to a constructor of A, and so they consider it calling B::operator A() to be "confusing".

Or perhaps it is indeed a bug... Given that this seems to be a rarely-used warning flag (it's not even in -Wall), and given how strange the "choosing x over y" messages are, that is quite possible.

Related