I am porting a large codebase from Linux/g++ to MacOS/Clang. I hit this compiler error in multiple places in Clang (where g++ builds successfully and does the right thing at run time):
error: initializer-string for char array is too long, array size is 1 but initializer has size X (including the null terminating character)
Note that I'm compiling for c++14.
I've reduced it to a manageable, reproducible case (eliminating all the uninteresting constructors and other methods), where the errors happen in the WTF
constructor's assignments:
#include <stddef.h>
struct StringLike
{
const char *str;
size_t len;
StringLike() : str(NULL), len(0) {}
template <size_t LEN_> StringLike(const char (&litAry)[LEN_]) noexcept :
str(litAry), len(LEN_ - 1) {}
StringLike &operator=(const StringLike &rhs)
{str = rhs.str; len = rhs.len; return *this;}
template <size_t LEN_> StringLike &operator=(const char (&strLit)[LEN_])
{str = strLit; len = LEN_ - 1; return *this;}
const char *data() const {return str;}
size_t length() const {return len;}
};
struct WTF
{
StringLike litStrs[3];
WTF()
{
litStrs[0] = {"Is "};
litStrs[1] = {"this "};
litStrs[2] = {"legal?"};
}
};
Yes, I know I could remove the braces from the litStrs[𝒏]
, and it does work, but I'd like to not change too many lines of code unnecessarily.
I can't figure how Clang is hallucinating a char array of size 1?!? I do see that if I comment out the StringLike
templated constructor, I get a similar error from g++; in the working case, g++ code is converting e.g. {"Is "}
to a StringLike
temporary via that constructor, then passing that to the operator=(const StringLike &rhs)
method (note that removing the braces from the WTF
constructor's assignments causes the templated operator=
method to be invoked directly, instead).
I'm not really sure where to look in the standard to figure out how the WTF
brace-enclosed assignments (operator=
, not construction) are supposed to be handled, so I'm not sure whether Clang or g++ is right (Clang has a better track record IMO, but I'm stumped what the correct behavior is here).
I used godbolt to verify that all versions of g++ accept this code, and all versions of Clang complain and give up.
Clang is correct, although the behavior is quite surprising.
[expr.assign] p8 explains:
A braced-init-list B may appear on the right-hand side of
- an assignment to a scalar of type
T
, in which case B shall have at most a single element. The meaning ofx = B
isx = t
, wheret
is an invented temporary variable declared and initialized asT t = B
.- an assignment to an object of class type, in which case B is passed as the argument to the assignment operator function selected by overload resolution ([over.assign], [over.match]).
Since StringLike
is a class type, the second bullet applies.
[over.match.oper] p2 explains that litStrs[0] = {"Is "}
would be translated into (litStrs[0]).operator=({"Is "});
, which Clang also rejects with the same error message.
The problem lies with how function template argument deduction works in this scenario; LEN_
would have to be deduced from the given {"Is "}
.
[temp.deduct.call] p1 explains:
In the
P′[N]
case, ifN
is a constant template parameter,N
is deduced from the length of the initializer list.
In the case of {"Is "}
and all your other attempts, the length of the initializer list is 1
, so Clang is not hallucinating. You're really trying to initialize a const char[1]
with a string literal of some other length at this point.
This behavior is quite surprising and possibly defective because usually, you can use extra braces when initializing an array with a string literal.
[dcl.init.string] explains that an array may be initialized by a string-literal, or by
an appropriately-typed string-literal enclosed in braces ([lex.string])
In other words, initialization of the parameter with "Is "
and {"Is "}
would be valid if it wasn't for template argument deduction breaking this.