7

Consider the following code:

#include <iostream>
#include <vector>
#include <array>

using namespace std;

typedef double (C_array)[10];

int main()
{ 
    std::vector<C_array> arr(10);

    // let's initialize it
    for (int i = 0; i < 10; i++)
        for (int j = 0; j < 10; j++)
            arr[i][j] = -1;

    // now make sure we did the right thing
    for (int i = 0; i < 10; i++)
    {
        for (int j = 0; j < 10; j++)
        {
            cout << arr[i][j] << " ";
        }
        cout << endl;
    }
}

I just found out from @juanchopanza https://stackoverflow.com/a/25108679/3093378 that this code should not be legal, since a plain old C-style array is not assignable/copyable/movable. However g++ flies through the code, even with -Wall -Wextra -pedantic. clang++ doesn't compile it. Of course if I try to do something like auto arr1 = arr;, it fails under g++, as it doesn't know how to copy arr into arr1.

I used g++4.9 from macports under OS X Mavericks. Live code here: http://goo.gl/97koLa

My questions are:

  1. Is this code illegal according to the standard?
  2. Is g++ so buggy? I keep finding lots of simple examples in which g++ blindly compiles illegal code, last was yesterday user-defined conversion operators precedence, compiles in g++ but not clang++ , and without too much effort, just experimenting with C++ for fun.
Community
  • 1
  • 1
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • Neither [g++ 4.8.1](http://rextester.com/JVQ6433) nor [clang 3.4](http://rextester.com/LAD69246) compile this for me. – David G Aug 03 '14 at 21:16
  • See http://goo.gl/97koLa And it looks like the experimental versions of `g++4.9` (the pre-releases) failed to compile, so they introduced a bug in the final release :)) – vsoftco Aug 03 '14 at 21:20
  • 5
    How have you determined that this is a bug? The fact that the code is invalid does not necessarily mean that it is ill-formed and requires a diagnostic. It could well be that the code is invalid, but that implementations are at liberty to silently accept the code anyway. –  Aug 03 '14 at 21:25
  • FWIW, clang 3.4.2 accepts the code too, when using libstdc++. –  Aug 03 '14 at 21:28
  • @hvd this is the first question that I asked actually, I am not sure it is a bug, so my previous comment about the bug may have been hasty. The code should be either standard-compliant or not, and if it is not, then it shouldn't be compilable. – vsoftco Aug 03 '14 at 21:29
  • 2
    In general, code that has undefined behaviour may silently be accepted by any implementation for any reason. It's not a bug if an implementation doesn't diagnose that. It's only a bug if the compiler fails to issue any of the standard-required diagnostics, the ones that render a program ill-formed (with a few exceptions where a diagnostic explicitly isn't required). And in general, if a template argument is invalid, then the behaviour is undefined. (I'm looking for a quote from the standard saying so, that's why I haven't posted this as an answer.) –  Aug 03 '14 at 21:34
  • @hvd, thanks! So you're saying that undefined behaviour may be rejected at compile time sometimes? And it is up to the compiler designers to decide? – vsoftco Aug 03 '14 at 21:36
  • 2
    @vsoftco Yes. It most likely will be accidentally accepted by libstdc++, and accidentally rejected by libc++, simply because libstdc++ happens to not make use of anything that doesn't work on arrays, but libc++ does. The C++ concepts feature was dropped from C++11 because it was too large a project, and that was about the only reasonable chance of detecting *exactly* which operations on types need to work and which don't, and of getting usable error messages for them. –  Aug 03 '14 at 21:47
  • @vsoftco: Undefined behavior literally means undefined. If it was required for compilers to diagnose it, it wouldn't be very undefined anymore. – GManNickG Aug 03 '14 at 22:56
  • @GManNickG I understand this very well, the only thing that I found strange is that a compiler can decide to reject code it believes to be undefined. I never saw `cout << *((char*)nullptr);` being rejected though, which is as undefined as what my stated problem may be. – vsoftco Aug 03 '14 at 23:04
  • @T.C. thanks, would this then work if I define a custom allocator? – vsoftco Aug 04 '14 at 00:12
  • 1
    @vsoftco Depends on the particular operation. A number of `vector` and `deque` operations require the `value_type` to be *CopyAssignable* or *MoveAssignable*, which a C array doesn't satisfy. But if an operation requires only *DefaultInsertable*, *CopyInsertable*, *MoveInsertable* or *EmplaceConstructible*, then you can use a custom allocator to make it work. – T.C. Aug 04 '14 at 00:18

1 Answers1

2

Your code is not valid C++03. First, the header <array> is not part of the C++03 standard library but it is also not needed here. Second, the construction of the vector object tries to call the constructor

explicit vector(size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());

However, the initialization of val fails for the same reason why you cannot write

C_array foo = C_array();

To the best of my understanding paragraph 2 in section 5.2.3 of the C++03 standard allows this notation only for non-array types:

The expression T(), where T is a simple-type-specifier (7.1.5.2) for a non-array complete object type or the (possibly cv-qualified) void type, creates an rvalue of the specified type, which is value-initialized (8.5; no initialization is done for the void() case).

Furthermore, g++-4.9.0 does also refuse to compile the code unless -std=c++11 is provided on the command line:

foo.cpp: In constructor ‘std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = double [10]; _Alloc = std::allocator<double [10]>; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = double [10]; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<double [10]>]’:
foo.cpp:11:32: error: functional cast to array type ‘std::vector<double [10]>::value_type {aka double [10]}’
 std::vector<C_array> arr(10);
...

As for C++11 the vector container offers an additional fill constructor:

explicit vector (size_type n);

This constructor requires that the template type be default-constructible (see section 23.3.6.2). To the best of my understanding this requirement is also not met in C++11 (see section 17.6.3.1) since in order to meet the requirement the expression C_array() must create a temporary object, which is also not valid in C++11 (again see section 5.2.3). I don't know whether the standard actually requires the compiler to reject the code or whether the compiler is allowed to compile it if one of the requirements is not met but the implementation of the standard library just does not need it. Maybe people who know more about C++11 can fill in the gaps here.

Aside from all that it is in my opinion not a good idea to try to use an array as a container element type since other container requirements are not met. For example C_array is not copy-insertable and thus the vector cannot be copied.

Concerning your second question: Feel free to browse the gcc bugzilla database at https://gcc.gnu.org/bugzilla/. However, accepting invalid code can also be on purpose e.g. in order to not break legacy code.

user1225999
  • 912
  • 8
  • 14
  • That `T` shall be `DefaultConstructible` is a defect; in later versions is (correctly) requires `DefaultInsertable`. I think an array type is `DefaultInsertable` for `std::allocator`, using placement-new. – dyp Sep 12 '14 at 13:18
  • libc++ complains about the destruction part; the default implementation uses an explicit/pseudo dtor call, which is not valid for an array type. One might guess since the array is trivially destructible, libstdc++ might not call that function. – dyp Sep 12 '14 at 13:21