10

The following example initializing a std::array <char, N> member in a constructor using a string literal doesn't compile on GCC 4.8 but compiles using Clang 3.4.

#include <iostream>
#include <array>

struct A {
  std::array<char, 4> x; 
  A(std::array<char, 4> arr) : x(arr) {}
};


int main() {
    // works with Clang 3.4, error in GCC 4.8.
    // It should be the equivalent of "A a ({'b','u','g','\0'});"
    A a ({"bug"});
    for (std::size_t i = 0; i < a.x.size(); ++i)
        std::cout << a.x[i] << '\n';

    return 0;
}

On first impression it looks like a GCC bug. I feel it should compile as we can initialize a std::array<char, N> directly with a string literal. For example:

std::array<char, 4> test = {"bug"}; //works

I would be interested to see what the Standard says about this.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
Ricky65
  • 1,657
  • 18
  • 22
  • Compiles cleanly with clang 3.5 (trunk 200501) with `-Wall -Wextra` and works as expected. The [error messages with gcc online](http://ideone.com/A0Wza7). – Ali Feb 04 '14 at 15:14
  • FWIW, `A a (std::array{"bug"});` is accepted by GCC, so "no known conversion for argument 1 from '' to 'std::array'" looks very strange to me. –  Feb 04 '14 at 15:20
  • I don't understand why {{"bug"}} works, you're asking for a 4-sized char array, not for a string (that's a different beast). Perhaps clang is being smart and fixing the user's code – Marco A. Feb 04 '14 at 15:32
  • There is a special rule in the standard that allows you to initialise a `char[N]` from a string literal. I assume this rule is coming in to play. [See here](http://ideone.com/G306q7). – Simple Feb 04 '14 at 16:08

1 Answers1

2

Yes, your code is valid; this is a bug in gcc.

Here's a simpler program that demonstrates the bug (I've replaced std::array<char, 4> with S and got rid of A, as we can demonstrate the bug just in function return (this makes the analysis simpler, as we don't have to worry about constructor overloading):

struct S { char c[4]; };
S f() { return {"xxx"}; }

Here we have a destination object of type S that is copy-initialized (8.5p15) from the braced-init-list {"xxx"}, so the object is list-initialized (8.5p17b1). S is an aggregate (8.5.1p1) so aggregate initialization is performed (8.5.4p3b1). In aggregate initialization, the member c is copy-initialized from the corresponding initializer-clause "xxx" (8.5.1p2). We now return to 8.5p17 with destination object of type char[4] and initializer the string literal "xxx", so 8.5p17b3 refers us to 8.5.2 and the elements of the char array are initialized by the successive characters of the string (8.5.2p1).

Note that gcc is fine with the copy-initialization S s = {"xxx"}; while breaking on various forms of copy- and direct-initialization; argument passing (including to constructors), function return, and base- and member-initialization:

struct S { char c[4]; };
S f() { return {"xxx"}; }
void g(S) { g({"xxx"}); }
auto p = new S({"xxx"});
struct T { S s; T(): s({"xxx"}) {} };
struct U: S { U(): S({"xxx"}) {} };
S s({"xxx"});

The last is particularly interesting as it indicates that this may be related to bug 43453.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • Are you sure that the Standard treats `array` specially in that it allows initializing with a braced enclosed string literal? IIRC, it does not specify the internal representation, so it seems to me that it's not safe to assume that you can initialize it with `= { string-literal }`. – Johannes Schaub - litb Feb 04 '14 at 21:33
  • @JohannesSchaub-litb. AFAIK 'array' is a thin wrapper around a 'char [N]' which itself can be initialized with a string literal so consequently it appears logical for 'array' to also be initialized using a string literal. However, although it appears logical to me it is ultimately up to the Standard. If people are told to prefer 'std::array' over built-in C arrays I feel it is important that string literal initialization is supported. – Ricky65 Feb 05 '14 at 00:46
  • @JohannesSchaub-litb you're right, of course; 23.3.2.1p2 only requires `array` to be an aggregate that accepts copy-initialization from a brace-enclosed initializer list; the `T elemens[N]` member is for exposition only, so it's possible to conceive of a class layout for `array` that doesn't contain directly a `char[N]` array member. – ecatmur Feb 05 '14 at 09:59
  • @ecatmur How else could `std::array` be implemented if it didn't contain a `char[N]` member? – Ricky65 Feb 05 '14 at 14:46
  • @Ricky65 `struct array { struct Elem { T elem } elems[N] }` would satisfy the aggregate and initialization requirement; it might need a little help to satisfy contiguity but the implementation would be able to provide the necessary guarantees. – ecatmur Feb 05 '14 at 14:57
  • 1
    @ecatmur The standard requires support for initialization from a initializer-list of a type convertible to `T`, not just for `T` directly. Consider `struct S { operator int() { return 0; } template operator T() = delete; };`, and then `array{S()}`. That would break on your implementation. –  Feb 05 '14 at 19:09
  • @hvd very good point - that means that `array` can't wrap its elements in a class type. There is though the possibility of an "optimisation" for small `N` where the elements are distinct members e.g. `template<> array { char c; char d; };` (the implementation would be able to supply appropriate layout guarantees). – ecatmur Feb 06 '14 at 09:09
  • @ecatmur That's sneaky, but does look valid. The implementation would need to ensure that it still fully supports pointer arithmetic (`&array[0] + 2 == &array[2]`): that is guaranteed by the standard, so an implementation cannot declare such code undefined just because the implementation of `array` does not use an array. Then again, the (non-normative) note in [array.overview] "The member variable `elems` is shown for exposition only, to emphasize that `array` is a class aggregate. The name `elems` is not part of array’s interface." can be read to imply that its type is part of the interface. –  Feb 06 '14 at 17:55
  • @ecatmur To clarify, in `struct S { int i; int j; };`, even if `&i + 1 == &j`, accessing `(&i)[1]` is still invalid (in C, anyway, and I don't think C++ opened this up). –  Feb 06 '14 at 17:57
  • @hvd that doesn't have to be invalid if the implementation feels like making it valid. that's the good of undefined behavior. But, anyway, there's still `array` – Johannes Schaub - litb Feb 06 '14 at 21:16
  • 1
    @JohannesSchaub-litb You're taking that out of context, I did mean invalid as far as the standard is concerned. The point was specifically that in order for that implementation of `array` to be valid, the implementation would need to specifically support such constructs. :) That said, I really like `array`, because that wouldn't even need a malicious implementation to not contain an array as its member. –  Feb 06 '14 at 21:26