I came across some strange code while reading a textbook (C++ Templates: The Complete Guide 2e) today that doesn't exactly make sense to me.
1 2 3 4 5 6 7 8 9 10 11 12 13
template<typename T>
class Stack {
private:
std::vector<T> elems;
// elements
public:
Stack (T elem)
// initialize stack with one element by value
: elems({std::move(elem)}) {}
};
Stack stringStack = "bottom"; // Stack<char const*> deduced since C++17
So in the following code, the constant "bottom" is deduced as a const char* since it is passed by value in the template as opposed to by reference. However, in the ctor-initializer, the value is casted to an rvalue reference and is moved into the vector.
The textbook originally had this implemented without the std::move, but stated that it would be better to std::move elem into the vector to avoid unnecessary copying.
However, this doesn't really make sense to me for const char*. Since it is a pointer, there is really no advantage to using move semantics versus value semantics because the actual data is never copied, only the pointer is. Furthermore, the data itself is const-qualified and is stored in eprom. So why does the author suggest to std::move() this value?
> So why does the author suggest to std::move() this value?
It is a template; in some instaniation of the template, the type T may have an efficient move constructor.
> but stated that it would be better to std::move elem into the vector to avoid unnecessary copying.
However, using the initialiser list constructor would make a copy anyway.
(First move into the initialiser list, then copy from the initialiser list; the inilialiser list is a list of const lvalues).
BTW, it's definitely NOT stored in eprom! It MAY be stored in memory that has been marked read-only by the OS, but that's not an EPROM. (Actually, I guess it could be stored in an EPROM on an embedded system.)
So the initializer list constructor would make a copy anyway? Then why does the author use std::move in the initializer list if it will make a copy anyway?
So the initializer list constructor would make a copy anyway? Then why does the author use std::move in the initializer list if it will make a copy anyway?
One copy is still avoided.
The object is moved into an element of the initializer_list but copied from the initializer_list into the vector. The copy occurs because elements of an initializer_list cannot be moved-from, because generally a move-operation modifies the moved-from object, and initializer_list stores objects of type const T.
See lines 20-24 of JLBorges's code for an example of how to avoid unnecessary copies inside the constructor.