1

Consider the following code:

#include <cstdio>
#include <initializer_list>

using namespace std;

class A {
    public:
    A(const char*, void*) { printf("const char*, void*\n"); };           // #1
    A(initializer_list<char*>) { printf("initializer_list<char*>\n"); }; // #2
};

void F(const A&) {};

int main(int, char**) {
    F({ "A", new char[256]() });
};

I have a function F that I can call with any argument that is_constructible to class A.

If I run the program, I see that constructor #2 is called, and I get a warning like: ISO C++11 does not allow conversion from string literal to 'char *const' for the first argument. C++ is automatically casting this parameter from a string literal to a char* in order for the call to match the signature initializer_list<char*>.

But the compiler could also try to cast the second parameter into a void* so that the call matches the signature const char*, void* instead.

Both invocations take 'the same number of steps' to go from invocation to 'matching signature' but for some reason the compiler chooses the latter.

I'd like to understand what is the rationale behind this choice, and also, if there's any chance to 'hint' the compiler to use constructor #1 whenever the first argument is a string literal.

On the big picture,

I want calls like: F({ "A", new char[256]() }) to choose constructor #1,

but calls like F({ (char*)"A", new char[256]() }) to choose constructor #2.

Compiler details: Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn) Target: x86_64-apple-darwin14.1.0 Thread model: posix

Ale Morales
  • 2,728
  • 4
  • 29
  • 42
  • Thanks, I saw the other answer, as expected initializer_list has precedence over regular signatures, but how would one workaround my particular issue? – Ale Morales Jul 17 '15 at 20:29
  • I'm re-opening my own close-as-duplicate. The "why" is completely answered at [Why is initializer_list constructor preferred here?](http://stackoverflow.com/questions/27144054), but that question does not address *if there's any chance to get around that*. – Drew Dormann Jul 17 '15 at 20:30
  • Interestingly, the `initializer_list` constructor shouldn't even be viable, because binding a string literal to a `char*` has been **removed** in the C++11 standard. The behaviour of the compilers is noncompliant. – dyp Jul 17 '15 at 20:31
  • Boils down to, how would you make an initializer_list that only accepts char* as arguments? – Ale Morales Jul 17 '15 at 20:31
  • @dyp Atm it just throws a warning, perhaps in C++14 it will become disabled for real. – Ale Morales Jul 17 '15 at 20:32
  • I suspect this is a compiler bug. Since "`ISO C++11 does not allow conversion from string literal to 'char *const'`", it should pick the other overload. – Tavian Barnes Jul 17 '15 at 20:32
  • @TavianBarnes Exactly what I thought. I already worked out a solution that disables the list through SFINAE, but there has to be a better (i.e. cleaner) way. – Ale Morales Jul 17 '15 at 20:34
  • What about `F( A ( "A", new char[ 256 ]() ) );`? – Hector Jul 17 '15 at 20:35
  • @almosnow "Atm it just throws a warning" The standard does not mandate compiler errors, it only mandates *diagnostic messages* for certain ill-formed programs (this can be warnings). However, the program is not ill-formed, and the behaviour here depends on the treatment of this conversion **as illegal**. That is, this conversion is not allowed since C++11, and the compilers allow it nevertheless as an extension, but that makes them deviate from the behaviour mandated in the standard for this case. – dyp Jul 17 '15 at 20:36
  • MSVC has the `/Zc:strictStrings` option, which makes it call constructor #1 in this example. I cannot find any similar option for gcc/clang, unfortunately. – dyp Jul 17 '15 at 20:40

1 Answers1

1

Initializer-list constructors like #2 take precedence over other constructors. As long as it's a viable constructor then they are considered despite a probably more viable non-initializer-list constructor. A string literal is an array of N const char, a conversion exist from a string literal to a char* as a legacy to C, but has been deprecated since C++03.

Since the conversion can happen anyway, the constructor is still used rather than #1. You can prevent this conversion by turning it into a non-literal (i.e by returning it from a function):

const char* operator"" _strong(const char* str, std::size_t) {
    return str;
}

int main() {
    F({ "A",        new char[256]() }); // #1
    F({ "A"_strong, new char[256]() }); // #2
}

Now the conversion to char* will fail and overload resolution then considers #1 as a candidate constructor.

David G
  • 94,763
  • 41
  • 167
  • 253
  • The conversion does not exist any more since C++11. The `initializer_list` ctor is therefore only called because of a nonconforming extension, which, as it seems, cannot be deactivated :( – dyp Jul 17 '15 at 20:42
  • Btw, you could also simply forward the string literal to an `char const[..]`. The conversion was only allowed for string literals, and should be suppressed for other arrays. [Live demo](http://coliru.stacked-crooked.com/a/59b491203f627ac9) – dyp Jul 17 '15 at 20:43
  • @dyp Thanks. Updated. – David G Jul 17 '15 at 20:47
  • I just realized that it's not allowed to declare a literal operator (template) which takes the raw string as an array parameter. How silly - this means one can't use any fixed size array storage based on the string length even though it is known at compile time m( – dyp Jul 17 '15 at 20:49
  • Increase weirdness factor by replacing `"A"_strong` with `+"A"` ;) Oops, gcc still converts that to `char*`, while clang does not. Nice corner case. – dyp Jul 17 '15 at 20:50