Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is this strange copy constructor error complaining about?

I'm on Visual Studio 2017. Recently, because I didn't like the non-conforming standards to C++ I went ahead and disabled the non-standard language extensions in the options. So far so good. Now I have a problem.

#include <iostream>
#include <vector>


struct Vertex
{
    Vertex(float pos) { }
    Vertex(Vertex& other) { }
};

std::vector<Vertex> arrayOfVertices;

int main()
{
    arrayOfVertices.emplace_back(7.f);
}

This would not compile in Visual Studio, the only error it gives is:

"an internal error has occurred in the compiler"

If I enable the language extensions it compiles fine. If I keep the language extensions disabled and make the copy constructor take a const Vertex& it compiles fine.

So I tried on GCC on some online compilers and if the copy constructor doesn't take a const reference argument it won't compile, giving various errors. The one that seemed to make the most sense was:

error: invalid initialization of non-const reference of type ‘Vertex&’ from an rvalue of type ‘Vertex’

I thought that copy constructors didn't have to be const, in my case I would like to modify something in the other reference. I know that non-const arguments can't take r-value references, but I tested it and it turns out that in vector::emplace_back() the copy constructor isn't called at all:

#include <iostream>
#include <vector>

struct Vertex
{
    Vertex(float pos) 
    { 
        std::cout << "Calling constructor\n";
    }
    Vertex(const Vertex& other) 
    { 
        std::cout << "Calling copy constructor\n";
    }
};

std::vector<Vertex> arrayOfVertices;

int main()
{
    arrayOfVertices.emplace_back(7.f); // Normal constructor called if const,
                                       // doesn't compile if non-const

    auto buff = malloc(sizeof(Vertex)); // Placement new
    new (buff) Vertex(7.f); // Normal constructor called whether const 
                            // or non-const. This is what I thought emplace_back did

}

So I have no idea what's going on. I would like to know firstly why this is happening if the copy constructor isn't being called, and also if there's a way to take a non-const in my copy constructor in this case, that is, using vector::emplace_back(), because it seems this problem is arising only using vector::emplace_back().

like image 866
Zebrafish Avatar asked Oct 14 '17 11:10

Zebrafish


People also ask

Why is my copy constructor not being called?

It's because of copy elision optimization by the compiler. Adding -fno-elide-constructors option to g++ while compiling will disable that optimization.

What is copy copy constructor?

The copy constructor is a constructor which creates an object by initializing it with an object of the same class, which has been created previously. The copy constructor is used to − Initialize one object from another of the same type.

What causes a copy constructor to be invoked?

In C++, a Copy Constructor may be called for the following cases: 1) When an object of the class is returned by value. 2) When an object of the class is passed (to a function) by value as an argument. 3) When an object is constructed based on another object of the same class.


2 Answers

The issue is that you don't have a move constructor.

When you request std::vector to emplace_back something, it must make sure that it has enough storage to construct the new object. Part of this routine is to instantiate a bunch of code that moves the elements from the old buffer to any newly allocated buffer, if it's required. That code will be instantiated by the template even if there won't be a reallocation at run-time.

Your class has a user defined copy constructor, so the move constructor is implicitly deleted. As such, the attempt to move any element in the original buffer into the new, will be turned into an attempt to copy, by overload resolution. Your focus on placement new is in fact a red herring, the real issue is evident in this simple example:

Vertex v1{7.f},
       v2{std::move(v1)};
       // Error, the xvalue from `move` can't bind to a non-const reference

You can silence the error rather easily by bringing the move constructor back, for instance, by explicitly defaulting it:

struct Vertex
{
    Vertex(float) 
    { 
        std::cout << "Calling constructor\n";
    }

    Vertex(Vertex&&) = default;

    Vertex(Vertex&) 
    { 
        std::cout << "Calling copy constructor\n";
    }
};

Never forget that in C++11, the rule of 0/3 became the rule of 0/3/5. Think carefully about move semantics for you classes too.

like image 168
StoryTeller - Unslander Monica Avatar answered Oct 22 '22 16:10

StoryTeller - Unslander Monica


Obviously it's a compiler bug if the compiler gives an internal error.

emplace_back(7.f) uses the constructor Vertex(float pos) to emplace the object -- the copy-constructor is not directly involved.

The actual reason for the error is different. When you emplace in a vector, in general, a reallocation may occur. If it does, then all objects in the vector must be relocated to a new place in memory.

Clearly, it's a runtime condition as to whether or not reallocation occurs. It's not feasible to have a compile error at runtime; so the error must occur upon compile-time use of emplace_back if the objects do not support reallocation; even if the vector happens to be empty for this call.

The Standard terminology is found in C++14 Table 87: in order to emplace into a vector, the element type must be MoveInsertable and MoveAssignable.

Without going into too much detail, the combination of a non-const copy-constructor, and no move-constructor, means the object fails the MoveInsertable requirement, as the rvalue argument in said requirement will not bind to the non-const lvalue reference.

like image 43
M.M Avatar answered Oct 22 '22 16:10

M.M