2

Consider two implementations of a class:

struct S1
{
    std::vector< T > v;
    void push(T && x) { v.push_back(std::move(x)); }
    void push(T const & x) { push(T(x)); }
    void pop() { v.pop_back(); }
    void replace(T && x) { pop(); push(std::move(x)); }
    void replace(T const & x) { replace(T(x)); }
};

struct S2
{
    std::vector< T > v;
    void push(T x) { v.push_back(std::move(x)); }
    void pop() { v.pop_back(); }
    void replace(T x) { pop(); push(std::move(x)); }
};

S1's push overloads express exactly what I want. S2's push is a way to express it in a less verbose way.

But I worry that there is a drawback connected with the excessive move-construction of objects.

Can modern compilers reduce the expression std::move(T(std::move(t))) to std::move(t) for some t where decltype(t) is T&? Can modern compilers optimize out unnecessary moves? Or is this prohibited by the standard?

Florian Kaufmann
  • 803
  • 6
  • 13
Tomilov Anatoliy
  • 15,657
  • 10
  • 64
  • 169
  • I do not see any `std::move(T(std::move(t)))` in provided code. I also fail do understand the meaning of `push(T(x));` in the second version of push for first flavor of S. – SergeyA Jan 08 '16 at 16:53
  • 1
    @SergeyA If you try to follow what is going on with some `T t;` passed as `S2 s2; s2.push(std::move(t))`, then you can see `std::move(T(std::move(t)))`. The second is just a method to reduce code duplication. – Tomilov Anatoliy Jan 08 '16 at 17:00
  • See also http://stackoverflow.com/questions/15600499/how-to-pass-parameters-correctly. It does not directly answer what you literally asked for, but what you probably intended to ask for. – Florian Kaufmann Jan 08 '16 at 20:40

1 Answers1

6

No, that elision is not legal, other than under as-if optimization.

Now, if foo() is an expression that returns a T, then S{}.push(foo()) can elide the move from the return value of foo() into the argument of push: only one move is done.

But if we S{}.push(std::move(foo()), the explicit std::move blocks the possibility of elision.

An approach that is often better is emplace based operations instead of push based operations.

template<class...Args>
void emplace(Args&&...args) {
  v.emplace_back( std::forward<Args>(args)... );
}

this lets you pass the parameters to construct the T to the object, and cause it to be directly constructed in the sink (the vector) rather than moved or copied into it.

Optionally:

template<class...Args,
  decltype(T(std::declval<Args&&>()...))* =0
>
void emplace(Args&&...args) {
  v.emplace_back( std::forward<Args>(args)... );
}

if you want SFINAE support. A comment saying "we expect a T to be constructed here", if not obvious, is also polite.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • `std::forward` is good thing, but in this case I should to add informative/defensive `std::enable_if_t< std::is_convertible< U, T >::value >`/`std::enable_if_t< std::is_same_v< std::decay_t< U >, T > >` SFINAE-guard or `static_assert`ion into function's body to let the user of this temlate function to know what is the (kind of) type should be passed to it. And finally it all became too verbose. – Tomilov Anatoliy Jan 08 '16 at 17:12
  • @Orient I took `...`. You can pass anything that can (together) construct a `T`, not just single arguments. And is_same is not going to work. The disadvantage to this is that `{}` construction doesn't work, but removing the `{}` will mostly fix that. And yes, you can add SFINAE boilerplate if needed. Or, say, a comment. – Yakk - Adam Nevraumont Jan 08 '16 at 18:27
  • Template member function implementation generally can't be hided from header. – Tomilov Anatoliy Jan 08 '16 at 18:46
  • @Orient True? Now, if you want to hide implementation, yet have the full power of `emplace`, you could write and use a `construct` type-erasure helper that takes anything that can construct a `T`, stores references to it, and lets you construct a `T` exactly once from it in a place of your choosing. Sadly, vector doesn't support being passed a "bound constructor" for push_back, so you'll either have to use raw backing storage or teach `construct` to interact with some (limited finite collection of) containers for emplacement. But that gets silly. – Yakk - Adam Nevraumont Jan 08 '16 at 20:54