Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How the following C++11 code should work?

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

like image 378
Sergey Shambir Avatar asked Jun 01 '16 15:06

Sergey Shambir


1 Answers

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>.

like image 104
Yakk - Adam Nevraumont Avatar answered Oct 08 '22 05:10

Yakk - Adam Nevraumont