initialize std::array without copying/moving elements - c++

#include <iostream>
#include <string>
#include <array>
class C {
private:
std::string a;
std::string b;
std::string c;
public:
C(std::string a_, std::string b_, std::string c_) : a{a_},b{b_},c{c_} {}
~C(){};
C(const C&) =delete;
C(const C&&) =delete;
const C& operator=(const C&) =delete;
const C& operator=(const C&&) =delete;
};
std::array<C,2> array = {C("","",""),C("","","")};
int main()
{}
this won't compile (Android Studio with NDK and clang) with a "call to deleted constructor of c" error. I know that I can e.g. use a std::vector and emplace_back() to construct an element directly inside the container, but in my code I want to only use fixed-sized containers and non-copyable/moveable objects for optimization. I'm probably missing sth basic here, but isn't there a way to initialize the std::array without first having to construct the individual elements and then copy them there?

You can use brace enclosed initializers instead of temporary c objects:
std::array<c,2> array = {{{"",""},{"",""}}};
or
std::array<c,2> array{{{"",""},{"",""}}};

It would become possible since C++17, from that for some specified cases copy elision is guaranteed.
Under the following circumstances, the compilers are required to omit
the copy- and move- constructors of class objects even if copy/move
constructor and the destructor have observable side-effects:
In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the
class of the destination, the initializer expression is used to
initialize the destination object:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
And for these cases, copy/move constructor is not required to be accessible.
When copy-elision takes place (until C++17) In those cases where
copy-elision is not guaranteed, if it takes place (since C++17) and
the copy-/move-constructor is not called, it must be present and
accessible (as if no optimization happened at all), otherwise the
program is ill-formed.
LIVE

Related

Why elements of std::initializer_list have to be copied?

cppreference says:
The underlying array is a temporary array of type const T[N], in which
each element is copy-initialized (except that narrowing
conversions are invalid) from the corresponding element of the
original initializer list. The lifetime of the underlying array is the
same as any other temporary object, except that initializing an
initializer_list object from the array extends the lifetime of the
array exactly like binding a reference to a temporary (with the same
exceptions, such as for initializing a non-static class member). The
underlying array may be allocated in read-only memory.
What's the reasoning behind this decision? Why is moving not ok?
What about copy-ellision?
struct A { A(const A&){ std::cout << "Oh no, a copy!\n"; } };
struct B { B(std::initializer_list<A> il); };
int main()
{
B b{ A{} };
return 0;
}
My compiler ellides the copy. But are these copies guaranteed to be ellided?
"Copy initialization" in C++ doesn't mean things will necessarily be copied. It's just a formal name for the constraints under which initialization will occur. For instance, when copy initializing, explicit c'tors are not candidates. So the following code will be ill-formed
#include <iostream>
#include <initializer_list>
struct A {
explicit A() = default;
A(const A&){ std::cout << "Oh no, a copy!\n"; }
};
struct B { B(std::initializer_list<A> il); };
int main()
{
B b{ {} };
return 0;
}
The single member in the list needs to be copy-initialized from {}, which entails calling the default c'tor. However, since the c'tor is marked explicit, this initialization cannot happen.
Copy elision is certainly possible pre-C++17, and is mandatory in C++17 onward in certain contexts. In your example, under a C++17 compiler, since you provide an initializer that is a prvalue (a pure rvalue, not an object), the initialization rules of C++ mandate that the target is initialized directly, without intermediate objects created. Even though the context is called "copy initialization", there are no superfluous objects.

Does the behavior of guaranteed copy elision depend on existence of user-defined copy constructor?

The following code behaves differently with or without user-defined copy constructor under GCC 8.0.1:
#include <cassert>
struct S {
int i;
int *p;
S() : i(0), p(&i) {}
// S(const S &s) : i(s.i), p(&i) {} // #1
// S(const S &s) : i(s.i), p(s.p) {} // #2
// S(const S &s) = delete; // #3
};
S make_S() {return S{};}
int main()
{
S s = make_S();
assert(s.p == &s.i);
}
With either of the commented user-defined copy constructors (even with #2, the one performing a simple shallow copy), the assertion will not fail, which means guaranteed copy elision works as expected.
However, without any user-defined copy constructor, the assertion fails, which means the object s in main function is not default-constructed. Why does this happen? Doesn't guaranteed copy elision perform here?
Quoting from C++17 Working Draft §15.2 Temporary Objects Paragraph 3 (https://timsong-cpp.github.io/cppwp/class.temporary#3):
When an object of class type X is passed to or returned from a function, if each copy constructor, move constructor, and destructor of X is either trivial or deleted, and X has at least one non-deleted copy or move constructor, implementations are permitted to create a temporary object to hold the function parameter or result object. ... [ Note: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
In your case, when I made both copy and move constructors defaulted:
S(const S &) = default;
S(S &&) = default;
assertion failed as well with GCC and Clang. Note that implicitly-defined constructors are trivial.

How to add elements to vector without invoking copy-constructor?

For some reason my temporary local object is always copy-constructed/destroyed when added to a vector which is causing problems due to nested std::reference_wrapper which are getting invalid because of the copy-construction and destruction afterwards (std::reference_wrapper targets are inside the object which gets destroyed -> so they are invalid in the copy-constructed object, if the source object is destroyed). But if possible I want to avoid the additional copying / destroying completely - that seems impossible, because whatever I tried always it wants to invoke the copy constructor (even using std::vector::emplace_back).
Considering this simple example (for easier understanding without std::reference_wrapper involved), it always tries to invoke the copy-constructor - I don't get why.
#include <vector>
class A{
public:
A(int a) : a(a){ }
int getInt() const{ return a; }
A(const A&) = delete; /* to deny copy-construction */
private:
int a;
};
int main(int argc, char* argv[]){
std::vector<A> vec;
vec.emplace_back(3); /* tries to call copy constructor */
vec.push_back(A(3)); /* tries to call copy constructor */
vec.push_back(std::move(A(3))); /* tries to call copy constructor */
return 0;
}
Any ideas what I'm missing here?
Per the Visual Studio 2013 documentation, emphasis mine:
"Rvalue references v3.0" adds new rules to automatically generate move constructors and move assignment operators under certain conditions. However, this is not implemented in Visual C++ in Visual Studio 2013, due to time and resource constraints.
Visual Studio 2013 is specified as using Rvalue references v2.1.
Note: As T.C. notes in the comments there is also an issue with explicitly disabling the copy constructor in your example. Per cppreference.com.
If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:
there are no user-declared copy constructors
there are no user-declared copy assignment operators
there are no user-declared move assignment operators
there are no user-declared destructors
(until C++14) the implicitly-declared move constructor is not defined as deleted due to conditions detailed in the next section
then the compiler will declare a move constructor as an inline public member of its class with the signature T::T(T&&).
A class can have multiple move constructors, e.g. both T::T(const T&&) and T::T(T&&). If some user-defined move constructors are present, the user may still force the generation of the implicitly declared move constructor with the keyword default.
Which means your example code also prevents auto generation of the move constructor (i.e., it has a user-declared copy constructor).
You need to explicitly declare your move constructor and/or move assignment operator. The following works for your example.
class A
{
public:
A(int a) : a(a) {}
A(const A&) = delete;
A(A&& other) : a(other.a) {}
int getInt() const { return a; }
private:
int a;
};
int main(int argc, char* argv[])
{
std::vector<A> vec;
vec.emplace_back(3);
vec.push_back(A(3));
vec.push_back(std::move(A(3)));
return 0;
}

type requirements for std::vector<type>

I am still confused about the requirements for a type to be used with a std::vector in C++11, but this may be caused by a buggy compiler (gcc 4.7.0). This code:
struct A {
A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
int X;
};
int main()
{
std::vector<A> a;
a.resize(4);
}
works fine and produces the expected output, indicating that the default ctor (explicitly given) is called (and not an implicit copy ctor). However, if I add a deleted copy ctor to the class, viz
struct A {
A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
A(A const&) = delete;
int X;
};
gcc 4.7.0 does not compile, but tries to use the deleted ctor. Is that correct behaviour or a bug? If the former, how to get the code working?
The C++11 standard does indeed require CopyInsertable as others have pointed out. However this is a bug in the C++11 standard. This has since been corrected in N3376 to MoveInsertable and DefaultInsertable.
The vector<T, A>::resize(n) member function requires MoveInsertable and DefaultInsertable. These roughly translate to DefaultConstructible and MoveConstructible when the allocator A uses the default construct definitions.
The following program compiles using clang/libc++:
#include <vector>
#include <iostream>
struct A {
A() : X(0) { std::cerr<<" A::A(); this="<<this<<'\n'; }
A(A&&) = default;
int X;
};
int main()
{
std::vector<A> a;
a.resize(4);
}
and for me prints out:
A::A(); this=0x7fcd634000e0
A::A(); this=0x7fcd634000e4
A::A(); this=0x7fcd634000e8
A::A(); this=0x7fcd634000ec
If you remove the move constructor above and replace it with a deleted copy constructor, A is no longer MoveInsertable/MoveConstructible as move construction then attempts to use the deleted copy constructor, as correctly demonstrated in the OP's question.
In C++11, the requirements depend on the operations performed. In the case of std::vector<T>::resize(), the requirement on T is that it be CopyInsertable into the vector.
From §23.3.6.3
void resize(size_type sz);
....
Requires: T shall be CopyInsertable into *this.
On ideone, I see one call to the default constructor. But four objects are being created, the others have to be constructed somehow. Actually, a prototype object is default constructed and then copied four times.
The C++11 Standard (section 23.3.6.3) says that "value initialized" objects will be inserted, but also requires that the type is copyable:
void resize(size_type sz);
Effects: If sz <= size(), equivalent to erase(begin() + sz, end());. If size() < sz, appends sz - size() value-initialized elements to the sequence.
Requires: T shall be CopyInsertable into *this.
There's no compiler bug here; it is your code which is wrong.
To use a class in a vector it should have a copy constructor/assignment operator or a noexcept move constructor/assignment operator. GCC is quite correct not to compile your example which doesn't have any of these.
How would you except the vector to do anything without being able to copy or move what it contains?
The reason that the first example works is that since you didn't define any copy or move constructors or assignment operators, you get the defaults. In the second example, since you explicitly deleted the copy constructor, you don't get any automatically generated constructors or assignment operators.
void resize(size_type) requires CopyInsertable, which means that an allocator should be able to construct-copy the type:
::new((void*)p)A(A());
This means that a copy-constructor is required. You should be able to bypass this with a custom allocator:
struct Allocator: public std::allocator<A> {
void construct(A *, const A &) { }
};
However libstdc++ does not respect this; see Should (in C++11) std::vector::resize(size_type) work for the default constructible value_type int[4]?

When is aggregate initialisation valid in C++11?

Lets say we have the following code:
#include <iostream>
#include <string>
struct A
{
A() {}
A(const A&) { std::cout << "Copy" << std::endl; }
A(A&&) { std::cout << "Move" << std::endl; }
std::string s;
};
struct B
{
A a;
};
int main()
{
B{A()};
}
Here, I believe struct A is not an aggregate, as it has both non-trivial constructors and also a std::string member which I presume is not an aggregate. This presumably means that B is also not an aggregate.
Yet I can aggregate initialize B. In addition this can be done without either the copy nor move constructor being called (e.g. C++0x GCC 4.5.1 on ideone).
This behavior seems like a useful optimization, particularly for composing together large stack types that don't have cheap moves.
My question is: When is this sort of aggregate initialization valid under C++0x?
Edit + follow up question:
DeadMG below answered with the following:
That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.
Note that when I change B to the following:
struct B
{
A a;
B(const A& a_) : a(a_) {}
B(A&& a_) : a(std::move(a_)) {}
};
A move is performed.
So if this is just uniform initialization and just calling the constructor and doing nothing special, then how do I write a constructor that allows the move to be elided?
Or is GCC just not eliding the move here when it is valid to do so, and if so, is there a compiler and optimization setting that will elide the move?
According to the new standard, clause 8.5.1 (Aggretates), a sufficiently simple type (e.g. no user-defined constructors) qualifies as an aggregate. For such an aggregate Foo, writing Foo x{a, b, ... }; will construct the members from the list items.
Simple example:
struct A
{
std::unordered_map<int, int> a;
std::string b;
std::array<int,4> c;
MyClass d; // Only constructor is MyClass(int, int)
};
// Usage:
A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, MyClass(4,4)};
// Alternative:
A x{{{1,-1}, {12, -2}}, "meow", {1,2,3,4}, {4,4}};
The object x is constructed with all the relevant constructors executed in place. No maps or strings or MyClasses ever get copied or moved around. Note that both variants at at the bottom do the same thing. You can even make MyClass's copy and move constructors private if you like.
That's not aggregate initialization at all, it's uniform initialization, which basically in this case means calling the constructor, and the no copy or move is probably done by RVO and NRVO.