Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::pair move not elided on definition?

I noticed something very strange with Visual Studio 2012: Defining a pair object like so:

    auto objp = pair<int, LogMe>();

will not elide the copy/move of the pair in VC11, this call will print:

LogMe::LogMe - def.ctor!
LogMe::LogMe - move.ctor!
LogMe::~LogMe - dtor!

that is, a temporary pair will be created and then moved into the objp variable. (declaring it as pair<...> obj; only logs the default ctor)

I have cross checked with my LogMe test object alone:

cout << "# Construct Object via auto obj = ...\n";
auto obj = LogMe();

# Construct Object via auto obj = ...
LogMe::LogMe - def.ctor!

and here the assignment will be elided.

This seems to be specific to VC11, as testing it in IDEOne (which uses gcc 4.8.1) shows that the extraneous move is elided always there.

What's going on here? Not being able to rely on the initialization copy being elided makes me nervous.

Note: Tests for release vs. debug version show the same result. (Which I would have expected, as copy-elision is performed independently of optimization flags in MSVC.)


Full sourcecode to test (See also the ideone link):

#include "stdafx.h"
#include <iostream>
#include <map>

using namespace std;

struct LogMe {
    std::string member;

    LogMe() {
        cout << __FUNCTION__ << " - def.ctor!" << endl;
    }
    ~LogMe() {
        cout << __FUNCTION__ << " - dtor!" << endl;
    }
    LogMe(LogMe const&) {
        cout << __FUNCTION__ << " - cpy.ctor!" << endl;
    }
    LogMe& operator=(LogMe const&) {
        cout << __FUNCTION__ << " - cpy.assign.op!" << endl;
        return *this;
    }
    LogMe(LogMe&&) {
        cout << __FUNCTION__ << " - move.ctor!" << endl;
    }
    LogMe& operator=(LogMe&&) {
        cout << __FUNCTION__ << " - move.assign.op!" << endl;
        return *this;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    {
        cout << "# Construct Object via auto obj = ...\n";
        auto obj = LogMe();
        cout << "# Construct pair<int, object> via auto objp = ...\n";
        auto objp = pair<int, LogMe>();
        cout << "# Construct pair<int, object> via pair objp2; ...\n";
        pair<int, LogMe> p2;
    }
    return 0;
like image 847
Martin Ba Avatar asked Oct 09 '13 21:10

Martin Ba


1 Answers

It appears it is not the move ctor, nor the templated move ctor that is causing the problem, but the presence of enable_if<is_convertable<... in the templated move ctor:

Testing with just an object, throwing auto and pair out of the test:

  • OK, copy/move elided:

            cout << "# Construct Object: auto obj = LogMe();\n";
            LogMe obj = LogMe();
    
            LogMe(LogMe&&) {
                cout << __FUNCTION__ ...
            }
    

And, with a test like so:

    cout << "# Construct Object: LogMeTempl obj = LogMeTempl();\n";
    LogMeTempl obj = LogMeTempl();
    cout << "# Construct Object: LogMeTempl obj2;\n";
    LogMeTempl obj2;
  • OK, copy move also elided:

    template<class Other>
    LogMeTempl(Other&& rhs
    //      , typename enable_if<is_convertible<Other, LogMeTempl>::value, void>::type ** = 0
    ) {
        cout << __FUNCTION__ << ...;
    }
    
  • Fail! Move ctor invoked!

    template<class Other>
    LogMeTempl(Other&& rhs
            , typename enable_if<is_convertible<Other, LogMeTempl>::value, void>::type ** = 0
    ) {
        cout << __FUNCTION__ << ...;
    }
    

    And note that the enable_if can be reduced to enable_if<true, void>::type** = 0 - if fact any additional defaulted parameter will do (e.g. , int defaulted_param_on_move_ctor = 0 and it will still prevent the move elision).

    This also extends to a type with a copy-ctor only that has a default argument. It won't be elided either. A quick cross-check with gcc shows there doesn't seem to be any such problem there.

Short Answer

Types with defaulted arguments in their copy/move ctor don't have their initialization copy/move elided.

I have added a bug on MS.connect for this issue.

I have also added a test-case for (N)RVO to IDEone. Even without the default argument, *N*RVO seems to work better in gcc than VC++.

like image 185
Martin Ba Avatar answered Nov 20 '22 21:11

Martin Ba