6

The following code outputs different results on various compilers:

  • 2,1,2 on Visual Studio 2013
  • 2,2,2 on Visual Studio 2015
  • 2,1,1 on GCC5/c++14
  • 2,2,2 on Clang 3.7 with Microsoft Codegen
  • 2,1,2 on Clang 3.6/c++11 under Ubuntu 14.04

Finally, how it should work according to C++ standard?

#include <iostream>
#include <vector>
#include <stdio.h>

using namespace std;

class Value;
using Array = std::vector<Value>;

class Value
{
public:
    Value(const Array &)
    {
    }

    Value(int)
    {
    }
};

void foo(Array const& a)
{
    printf("%d\n", (int)a.size());
}

int main()
{
    Array a1 = { 1, 2 };
    foo(a1);
    foo(Array{ a1 });
    foo(Array({ a1 }));
}

P.S. The same issue reveals with json_spirit library from this article: http://www.codeproject.com/Articles/20027/JSON-Spirit-A-C-JSON-Parser-Generator-Implemented

Sergey Shambir
  • 1,562
  • 12
  • 12

2 Answers2

5

Your program is ill-formed in C++11, as you have created a std::vector type with an incomplete type as an argument. The value type of a vector must be complete when you create the std::vector<T> type.

As an ill-formed program (and I'm not aware of a requirement for a diagnostic), any and all behavior is legal under the standard.

The requirement that vector's T be a complete type is probably over-specified (there is really no convincing reason to have that requirement), but it exists. Asking the same question in C++1z will lead to a different answer, as that requirement was relaxed.


Ignoring that issue, philosophically:

Array a1 = { 1, 2 };

this should generate a std::vector with two elements.

foo(a1);

This should pass a1 to foo, as the types match exactly.

foo(Array{ a1 });

Here we have {}. My rule of thumb with {} is "if it is non-empty, and it can match an initializer_list constructor, it must".

As a1 can be converted to a Value, Array{ a1 } is an array of one Value.

foo(Array({ a1 }));

Here we look at an argument of the constructor Array that can be called with { a1 }. Both the std::initalizer_list<Value> and the Array&& constructors can be.

Which is called should not matter: The Array&& constructor in turn sends the {a1} to the std::initalizer_list<Value> constructor.

So my opinion is that it should print 211. This is, however, merely an opinion on what the could ought to do, not an analysis of what the standard says it should do, as the C++11 standard quite clearly states your program is ill-formed.

On the other hand, hiding the copy constructor seems rude.

On the final hand, you did write a simply insane type. Insane behavior is to be expected.

A more practical concern might be when the Value type has a template constructor that happens to also match a std::vector<Value>.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

In C++11, compilers' determination to match braced initializers with constructors taking std::initializer_lists is so strong, it prevails even if the best-match std::initializer_list constructor can't be called (Scott Meyers).

Array a1 = { 1, 2 }; // Call Initiliazer list constructor foo(a1); // print 2

foo(Array{ a1 }); // Call Initiliazer list constructor.  Print 1

foo(Array({ a1 })); // Call Initiliazer list constructor.  Print 1

According to the standard, the result must be: 2,1,1

Yakk's answer mention that the program is ill-formed in C++11. But this post is very interesting.

Community
  • 1
  • 1
chema989
  • 3,962
  • 2
  • 20
  • 33