4

I tried the following code:

#include <iostream>

struct test{
    test(){}
    test(test const &){
        std::cout<<__LINE__<<"\n";
    }
    test(test&&){
        std::cout<<__LINE__<<"\n";
    }
};

#include <vector>
#include <utility> // std::move
int main(){
    auto&& tmp = test();
    std::vector<test> v;
    v.push_back(tmp);
    std::cout<<__LINE__<<"\n";
    v.push_back(std::move(tmp));

    return 0;
}

The vs2013 compiler output:

6 // copy

18

9 // move

9 // move

The g++ and clang++ output:

6 // copy

18

9 // move

6 // copy

My problems are:

  1. Is the type of tmp test&&? Is tmp a rvalue?

  2. If the type of tmp is test&&, why didn't the first push_back use move constructor?

  3. Where did the last output come from? Why did vs2013 and g++ output different results?

Thanks.

Answer for 3rd problem: It comes from the reallocation as commented by andrew.punnett.

juanchopanza
  • 223,364
  • 34
  • 402
  • 480
cqdjyy01234
  • 1,180
  • 10
  • 20

2 Answers2

4

Is the type of tmp test&&?

Yes and no. tmp is an rvalue reference variable of type test&&, but the identifier tmp taken as an expression has type test and value category lvalue. The & is never part of the type of an expression.

Is tmp a rvalue?

No. Any use of an identifier is an lvalue expression, even the name of an rvalue reference. Rvalue reference variables are accessed essentially identically to lvalue reference variables; only decltype(tmp) can tell the difference. (Often you would use decltype((tmp)) to avoid telling the difference.)

If the type of tmp is test&&, why didn't the first push_back use move constructor?

Because the name of an rvalue reference is still an lvalue. To get an rvalue expression, use move(tmp).

Where did the last output come from? Why did vs2013 and g++ output different results?

Clang and GCC only made room for one object in the vector by default. When you added the second object, the vector storage was reallocated causing the objects to be copied. Why weren't they moved? Because the move constructor is not noexcept, so if it threw an exception, it would be impossible to undo the reallocation.

As for the two moves by MSVC, there are two possibilities, which you can distinguish by experimentation — I don't have a copy handy.

  1. MSVC reserved enough space inside the vector for two objects by default. The second move is from an internal local variable.
  2. MSVC ignored the requirement that the move constructor be noexcept and called it to perform relocation anyway. This would be a bug, but in this case it's covering up a common mistake.

If you replace vector with deque, you won't see any more copies, because deque is not allowed to assume copyability.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 3
    Mostly correct, but the extra calls to the constructor are just because the vector is being relocated not use of a "temporary internal variable". For example try v.reserve(2) just after creation of the vector. – andypea Feb 18 '14 at 07:12
  • Isn't there some reference collapsing trickery going on here, i.e. `tmp` is whatever is returned by the RHS? It is the same as the whole "universal references" thing. – juanchopanza Feb 18 '14 at 07:12
  • So test&& has no superiority against test&? – cqdjyy01234 Feb 18 '14 at 07:13
  • @andrew.punnett Thanks, I just assumed GCC and Clang had reserved sufficient space. (`test` should only be one byte, and there's seldom an advantage to dynamically allocating less than a few words' worth.) Aaand, an MSVC bug looks likely. Typical. – Potatoswatter Feb 18 '14 at 07:24
  • @user1535111 Depends what you mean by "superiority". The big difference between rvalue references and lvalue references is how they're initialized. Try substituting `auto &` and see what happens. – Potatoswatter Feb 18 '14 at 07:36
  • @user1535111 It's a different thing. `test&&` can bind to an rvalue, `test&` can't. – Angew is no longer proud of SO Feb 18 '14 at 07:37
  • @FredOverflow Not true. `auto &&` in a local declaration is deduced the same as a template parameter. – Potatoswatter Feb 18 '14 at 08:00
  • @FredOverflow I am pretty sure it could bind to anything. That is the point of using it. – juanchopanza Feb 18 '14 at 08:00
2

Visual Studio do not yet support the noexcept keyword and is likely not compliant to the exception safety of push_back. Also, the additional output is a result of the difference on capacity computation on grow.

#include <iostream>
#include <vector>
#include <utility> // std::move

struct Except{
    Except(){}
    Except(Except const &) {
        std::cout<< "COPY\n";
    }
    Except(Except&&)  {
        std::cout<< "MOVE\n";
    }
};

struct NoExcept{
    NoExcept(){}
    NoExcept(NoExcept const &) noexcept {
        std::cout<< "COPY\n";
    }
    NoExcept(NoExcept&&) noexcept {
        std::cout<< "MOVE\n";
    }
};

template <typename T> void Test( char const *title,int reserve = 0) {
    auto&& tmp = T();
    std::cout<< title <<"\n";
    std::vector<T> v;
    v.reserve(reserve);

    std::cout<< "LVALUE REF ";
    v.push_back(tmp);
    std::cout<< "RVALUE REF ";
    v.push_back(std::move(tmp));
    std::cout<< "---\n\n";
}
int main(){
    Test<Except>( "Except class without reserve" );
    Test<Except>( "Except class with reserve", 10 );
    Test<NoExcept>( "NoExcept class without reserve" );
    Test<NoExcept>( "NoExcept class with reserve", 10 );
}

And the result within clang :

Except class without reserve
LVALUE REF COPY
RVALUE REF MOVE
COPY
---

Except class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---

NoExcept class without reserve
LVALUE REF COPY
RVALUE REF MOVE
MOVE
---

NoExcept class with reserve
LVALUE REF COPY
RVALUE REF MOVE
---
galop1n
  • 8,573
  • 22
  • 36