6

Given the following C++14 code:

struct A { /* heavy class, copyable and movable */ }; // complex code to create an A A f(int); A g(int); A h(int); const std::vector<A> v = { f(1), g(2), h(3) }; 

I know the A's in the initializer_list are copied into the vector, instead of being moved (there are a lot of questions in stackoverflow about this).

My question is: how can I move them into the vector?

I've only been able to do the ugly IIFE (that keeps v const) and just avoids initializer_list:

const std::vector<A> v = []() { std::vector<A> tmp; tmp.reserve(3); tmp.push_back( f(1) ); tmp.push_back( g(2) ); tmp.push_back( h(3) ); return tmp; }(); 

Is it possible to make this elegant and efficient?

PD: v has to be a std::vector<A> for later use

13
  • 1
    Why do you have a heavy class that requires free functions to create? Commented Apr 28, 2017 at 10:50
  • 1
    @PasserBy: That's not unusual or strange. If lots of setup is required, you want a factory function doing the work, not a constructor. Commented Apr 28, 2017 at 12:01
  • 3
    Frankly I think initializer_list's behaviour is ludicrous here. C++ seems to be increasingly designed around implementation constraints rather than what actually makes sense to the user. I understand that what we read on screen is not what the compiler is doing, but as a C++ user it is blindingly obvious that this particular utterance of { f(1), g(2), h(3) } will never be used again, so its items should be moveable-from. It's frustrating to be repeatedly told that's impossible because of the reusability of std::initializer_list or w/e, when actually we should not have to care about that. Commented Apr 28, 2017 at 12:13
  • 2
    @Boundary Cannot +1 enough. initializer_list is awful Commented Apr 28, 2017 at 13:04
  • 2
    About function vs constructor: I need the complicated work to be done in a function, that has nothing to do with the responsibilities of the class. The constructor should just establish the class invariants, no more. Also, I have several of those factory functions, some not even written by the class author. Commented Apr 28, 2017 at 13:16

2 Answers 2

7

Not sure if it counts as "elegant" but you could use an array (or std::array) which uses aggregate initialization that doesn't suffer from this problem and move_iterator to move the values into the vector.

std::array<A, 3> init = { f(1), g(2), h(3) }; std::vector<A> v{std::make_move_iterator(init.begin()), std::make_move_iterator(init.end())}; 

Live demo.

Sign up to request clarification or add additional context in comments.

11 Comments

The risk here is that init is used after it has been moved from.
@MaximEgorushkin I assume the scope of init is kept as small possible. Just create a function that creates your vector and returns it.
@MaximEgorushkin If you were really worred, you could encapsulate it in a lambda: auto v = []{ std::array<A, 3> init = { f(1), g(2), h(3) }; return std::vector<A>{std::make_move_iterator(init.begin()), std::make_move_iterator(init.end())};}();
I like removing unnecessary risks and complexity. Lambda adds complexity here.
Yeah and that's really no better than the original workaround.
|
2

You ask a C++14 solution, so you can use variadic lambda function with auto arguments.

Following the example of your lambda function...

#include <vector> struct A { }; A f (int) { return {}; } A g (int) { return {}; } A h (int) { return {}; } int main () { static const auto getVA = [](auto && ... args) { using unused = int[]; std::vector<A> ret; ret.reserve(sizeof...(args)); (void)unused { 0, (ret.emplace_back(std::forward<decltype(args)>(args)), 0)... }; return ret; }; auto va = getVA(f(1), g(2), h(3)); } 

If you prefer old type (not-lambda) function, or you want a solution that works also with C++11, getVA can be written as follows

template <typename ... Args> std::vector<A> const getVA (Args&& ... args) { using unused = int[]; std::vector<A> ret; ret.reserve(sizeof...(args)); (void)unused { 0, (ret.emplace_back(std::forward<Args>(args)), 0)... }; return ret; } 

7 Comments

Nice solution, thanks! IMHO, the (void)unused+comma operator+zero is a bit tricky for everyday use, specially for something that should be easy. One question: why is the std::move() necessary when calling getVA?
std::forward<A> is nonsensical in both code snippets, since A is a concrete type. It should be std::forward<decltype(args)>(args) and std::forward<Args>(args), respectively (and args needs to be made a forwarding reference in the latter).
@ildjarn - yes, I see the problem; on the other hand, the functions are returning a std::vector<A>, so (it seems to me) make sense a forward to A; maybe I could receive auto arg0, auto ... args and define ret as std::vector<decltype(arg0)> (make sense for you?) but (seems to me that) is inelegant and nobody guarantee that the types of the following arguments are compatible with the type of the first one.
@dats - yes: my solution is a little tricky; the solution from Chris Drew is simpler and more elegant (I think) for this problem but I suggest you to understand how work this solution because it's useful with a lot of problem based on variadic arguments (but starting from C++17 is a lot simpler working with variadic arguments). Anyway: the std::move() is an error from mine: as pointed by Barry, the value returned by f(), g() and h() are already rvalues. So std::move() is highly superflous (and I've removed it).
@max66 : The only forwarding that can possibly occur is from forwarding references, using the same type as said forwarding reference (forwarding relies on/works because of reference collapsing). The idea is to pass each Args as an rvalue or lvalue of the source type properly (not the destination type), regardless of whether the argument type is an A or not, so that the correct A ctor will be invoked, regardless of whether that's a copy ctor, move ctor, or (most importantly for my point) implicit conversion ctor (emplace_back should be used here, not push_back).
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.