Insert templated class in std::map with construction at insertion - c++

I am trying to insert an instance of a templated class that does not have a copy constructor into a map. The code below does not work, because at the emplace function the compiler wants to call the copy constructor. I do not understand why, because I understood from the C++ reference that emplace does not move or copy:
Careful use of emplace allows the new element to be constructed while
avoiding unnecessary copy or move operations.
This is my code:
#include <map>
#include <string>
template<typename T> class Class_a
{
public:
Class_a(T t1, T t2) : t1_(t1), t2_(t2) {}
~Class_a() {}
Class_a(const Class_a&) = delete;
Class_a& operator=(const Class_a&) = delete;
Class_a(Class_a&&) = delete;
private:
const T t1_;
const T t2_;
};
template<typename T>
using Class_a_map = std::map<std::string, Class_a<T>>;
int main()
{
Class_a_map<double> class_a_map;
std::string name = "test";
double number1 = 42;
double number2 = 43;
class_a_map.emplace(name, Class_a<double>(number1, number2));
return 0;
}

You can use std::piecewise_construct and std::forward_as_tuple to create your objects in place.
class_a_map.emplace(
std::piecewise_construct,
std::forward_as_tuple(name),
std::forward_as_tuple(number1, number2)
);
live wandbox example
std::map::emplace perfectly forwards a bunch of arguments to the underlying std::pair used for key/value storage. std::pair::pair has an overload that takes an std::piecewise_construct_t as its first argument and then two std::tuple instances: the first one will be used to construct .first in place, the second one will be used to construct .second in place.
From cppreference, regarding std::pair's piecewise constructor:
Forwards the elements of first_args to the constructor of first and forwards the elements of second_args to the constructor of second. This is the only non-default constructor that can be used to create a pair of non-copyable non-movable types.

Related

How to use std::find_if with a vector of unique pointers?

How do you use algorithms like std::find_if with a vector of unique pointers? For instance:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
class Integer {
public:
explicit Integer(int i): i_(i){};
int get() const{
return i_;
}
private:
int i_;
};
using IntegerPtr = std::unique_ptr<Integer>;
int main() {
IntegerPtr p1 = std::make_unique<Integer>(4);
IntegerPtr p2 = std::make_unique<Integer>(5);
IntegerPtr p3 = std::make_unique<Integer>(6);
std::vector<IntegerPtr> vectorOfIntegerPointers({
std::move(p1),
std::move(p2),
std::move(p3),
});
int i = 5;
auto first_index_larger_than_i = std::find_if(vectorOfIntegerPointers.begin(), vectorOfIntegerPointers.end(), [&](IntegerPtr s) {
return s->get() > i;
});
std::cout << first_index_larger_than_i.get() << std::endl;
return 0;
}
Fails with
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/memory:1881:31: error: call to implicitly-deleted copy constructor of 'std::__1::unique_ptr<Integer, std::__1::default_delete<Integer> >'
::new((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are 2 problems in the code both leading to an attempt to copy the non-copyable unique_ptr.
unique_ptr can't be passed to vector(initializer_list) constructor, because initializer_list wraps its elements as const objects, and a const object cannot be moved-from. So the move-constructor doesn't participate in overload resolution, leaving only the copy constructor as a candidate, which later fails to compile with the error you saw: "call to implicitly-deleted copy constructor".
So you have to use another solution to construct vector<unique_ptr>, for example using push_back:
std::vector<IntegerPtr> vectorOfIntegerPointers;
vectorOfIntegerPointers.push_back(std::make_unique<Integer>(4));
vectorOfIntegerPointers.push_back(std::make_unique<Integer>(5));
vectorOfIntegerPointers.push_back(std::make_unique<Integer>(6));
Or write a wrapper to hold unique_ptr as mutable members (example).
[&](IntegerPtr s) { ... } used in std::find_if attempts to take instances of unique_ptr by-value. But unique_ptr is not copyable, hence the same error.
A quick fix is to take them by-reference instead:
[&](IntegerPtr const& s) { ... }

C++ how to create move semantics for a class that is initialized as a vector

I want to define a move constructor on a class that will be instantiated in a std::vector. However, the move constructor seems to interfere with the initialization of the vector.
#include <iostream>
#include <vector>
class cell
{
private:
int m_value;
public:
void clear() {m_value = 0;}
cell(int i = 0): m_value(i) {}
cell(const cell&& move): m_value(move.m_value) {} //move constructor
cell& operator= (const cell& copy)
{
if (&copy == this) return *this;
clear();
m_value = copy.m_value;
return *this;
}
int getValue() const {return m_value;}
};
int main()
{
cell mycell {3}; // initializes correctly
std::vector<cell> myVec {1, 2, 3, 4}; // compile error.
return 0;
}
I have done quite a bit of research but haven't been able to find the solution to this problem. Quite new to C++ programming.
edit: my class will eventually have a lot more than m_value in it, including some non-fundamental types, hence I don't want to use the default copy constructor.
The problem is that in std::vector constructor with parametr of type std::initializer_list elements are copy-initialized, therefore, copy constructor is requried for your cell class (see, e.g., Why copy constructor is called in std::vector's initializer list? for some relevant discussion). Otherwise, you would be fine with move constructor:
std::vector<cell> myVec;
myVec.push_back(1);
...
cell(const cell&& move): m_value(move.m_value) {}
Is wrong in several ways. First, the const means that it applies to a move from const objects. This is rarely what you want.
edit: The const move-constructor does not overcome the constness of the std::initializer_list objects, since the vector constructor never moves elements from the initializer_list regardless of the avialability of a const move constructor.
Second, it should have a noexcept specifier, otherwise the vector will prefer not to use the move constructor when it increases capacity. To have it work, you should replace the code with:
cell(cell&& move) noexcept: m_value(move.m_value) {}
Third, it is better to use the default, when possible:
cell(cell&& move) noexcept = default;

C++ vector emplace_back calls copy constructor

This is a demo class. I do not want my class to be copied, so I delete the copy constructor. I want vector.emplace_back to use this constructor 'MyClass(Type type)'. But these codes won't compile. Why?
class MyClass
{
public:
typedef enum
{
e1,
e2
} Type;
private:
Type _type;
MyClass(const MyClass& other) = delete; // no copy
public:
MyClass(): _type(e1) {};
MyClass(Type type): _type(type) { /* the constructor I wanted. */ };
};
std::vector<MyClass> list;
list.emplace_back(MyClass::e1);
list.emplace_back(MyClass::e2);
The copy constructor is required by vector so that it can copy the element when it need to grow its storage.
You can read the document for vector
T must meet the requirements of CopyAssignable and
CopyConstructible. (until C++11)
The requirements that are imposed on
the elements depend on the actual operations performed on the
container. Generally, it is required that element type is a complete
type and meets the requirements of Erasable, but many member functions
impose stricter requirements. (since C++11) (until C++17)
The
requirements that are imposed on the elements depend on the actual
operations performed on the container. Generally, it is required that
element type meets the requirements of Erasable, but many member
functions impose stricter requirements. This container (but not its
members) can be instantiated with an incomplete element type if the
allocator satisfies the allocator completeness requirements.
Some logging can help you understand what's going on
For this code
class MyClass
{
public:
typedef enum
{
e1 = 1,
e2 = 2,
e3 = 3,
} Type;
private:
Type _type;
public:
MyClass(Type type): _type(type) { std::cout << "create " << type << "\n"; };
MyClass(const MyClass& other) { std::cout << "copy " << other._type << "\n"; }
};
int main() {
std::vector<MyClass> list;
list.reserve(2);
list.emplace_back(MyClass::e1);
list.emplace_back(MyClass::e2);
list.emplace_back(MyClass::e3);
}
The output is
create 1
create 2
create 3
copy 1
copy 2
So you can emplace_back does use the desired constructor to create the element and call copy constructor when it need to grow the storage. You can call reserve with enough capacity upfront to avoid the need to call copy constructor.
If for some reason you really don't want it to be copy constructible, you can use std::list instead of std::vector as list is implemented as linked list, it doesn't need to move the elements.
http://coliru.stacked-crooked.com/a/16f93cfc6b2fc73c
Just a precision for the issue. If we don't want objects copy construction to be used when a reallocation of the container occurs, it is indeed possible with a move constructor but only if it has the noexcept specification.
Containers refuse to move construct elements if the constructor might throw an exception because it could lead to a container in a bad state that cannot be cleaned. That's the reason why it is generally a good practice to specify a move constructor as noexcept when we are sure it will never throw any exceptions.
According to https://en.cppreference.com/w/cpp/container/vector/emplace_back, the value_type of a std::vector<T> needs to be MoveInsertable and EmplaceConstructible. MoveInsertable in particular requires a move constructor or a copy constructor.
So, if you don't want your class to be copied, you should add an explicit move constructor. You can use = default to use the compiler-provided default implementation that just moves all fields.
Full example
#include <vector>
class MyClass
{
public:
typedef enum
{
e1,
e2
} Type;
private:
Type _type;
MyClass(const MyClass& other) = delete; // no copy
public:
MyClass(): _type(e1) {};
MyClass(MyClass&&) noexcept = default; // < the new move constructor
MyClass(Type type): _type(type) { /* the constructor I wanted. */ };
};
int main() {
std::vector<MyClass> list;
list.emplace_back(MyClass::e1);
list.emplace_back(MyClass::e2);
}
Note
Note that you can get a very confusing
error: use of deleted function ‘MyClass::MyClass(const MyClass&)
with C++17 when you use
auto x = list.emplace_back(MyClass::e1);
instead of
auto& x = list.emplace_back(MyClass::e1);
even with the move constructor.

c++ custom container pass {} list

I am basically remaking the set container and I am wondering how I pass a brace-enclosed initializer list to the container.
mySet<int> myset = {"test", "test 2"};
I tried to overload the operator= to take a list as a parameter.
Despite the presence of the = here, this is constructing a set, so what you need is a constructor (that takes an std::initializer_list as its parameter).
template<class T>
class myset {
public:
// ...
mySet(std::initializer_list<T> init) {
for (T const &t : init)
insert(t);
}
};
You need to implement a constructor which take a std::initializer_list as its parameter, such as:
template <class T>
class mySet {
public:
mySet(std::initializer_list<T> l) {
// ...
}
};
Define a constructor that takes a std::initializer_list as a parameter, like
mySet(std::initializer_list<T> args){/*...*/}
operator= is invoked only on assignments, not on copy initialization (your case). In this latter case the constructor is invoked on the rhs, followed by a copy (or move), which is usually elided, but nevertheless the copy or move ctors must be accessible.

Finding typeid of a template parameter

The print statement in the constructor's definition doesn't get printed, isn't the constructor calling correct in main? I know I am missing some point here, please point out.
#include <iostream>
#include <typeinfo>
template <typename T> class List
{
public:
template <typename T2> List (List<T2> const&);
};
template <typename T> template <typename T2> List <T> :: List (List <T2> const&)
{
std :: cout << "\nType name:" << typeid (T2).name();
}
int main ()
{
List <int> kk (List <int>);
return 0;
}
There are a couple of things wrong in your code that you might not be aware of.
List<int> kk( List<int> );
That line is not a variable definition, but rather the declaration of a function that takes a List<int> as argument and returns a List<int>, so that effectively will not call any constructor. That is know as the most-vexing-parse (you can look at different versions of it by searching in SO, or in the C++ FAQ lite)
The second issue is that you cannot possibly create any instance of the an instantiated type of List, the reason being is that the only constructor that you are providing is a templated constructor that takes a second List<U> as argument. That effectively disables the default constructor, so the only way of creating a List<T> is by already having a List<U>, and that is not possible. You can add the default constructor back:
template <typename T>
class List {
public:
List() {}
template <typename U>
List( List<U> const & ) {} // prefer const& as that will avoid unnecessary copying
};
And now you can write:
List<int> l = List<int>(); // this will call List<int>::List( List<int> const & )
And yet, that will still not call the constructor you want. The reason is a little obscure, but when copy constructing an element of a template, the compiler will not use a templated constructor. In the code above, it will implicitly define a copy constructor by doing member-wise copy constructor of the methods and call that generated constructor. That means that in most occasions where you want to provide a templated constructor you want to also provide a non-templated copy constructor.
To actually call that constructor you would have to provide a different type:
List<int> l = List<double>();
Since the types actually differ, the compiler cannot copy construct, will find that the provided templated constructor is the best overload candidate and call it.
As well as the "most vexing parse" identified by David:
you need to have at least one more constructor to create the original List object to be passed to the copy constructor,
you need to vary the parameter type in order to have the templated copy constructor invoked: as is you'll match the implicitly declared List(const List&) copy constructor instead.
So:
#include <iostream>
template <typename T>
struct X
{
X() { std::cout << "X()\n"; }
// implicitly like this anyway...
// X(const X& rhs) { std::cout << "X(X&)\n"; }
template <typename U>
X(const U& u) { std::cout << "U\n"; }
};
int main()
{
X<int> x;
X<int> y(x);
}
What are you trying to do with this statement:
List <int> kk (List <int>);
(It actually declares a function, and can't be anything but
a function declaration.)
In order to see output from the copy constructor, you've got to
invoke the copy constructor somehow. Which means having an
object to copy. Which isn't possible with the code you've
given: since you've explicitly declared a constructor, the
compiler will not provide a default constructor, and you have no
other constructor with which to create an object. So you have
no way of creating anything to copy. If you add a
List() {}
to the class, and write:
List<int> kk((List<int>());
, you might get something, but the compiler is allowed to elide
the copy here, so more likely there will be no output. Try:
List<int> a;
List<int> b(a);
Or just put your output in the default constructor.