Search code examples
c++c++11copy-elision

Copy elision of map iterator pair value


In the following MVE, does the return value of the Get function qualify for copy elision?

EDIT

I've change the example somewhat. With Visual Studio 2017 in both Debug and Release builds I see a copy construction on the return statement. Hopefully this is just because I've messed up my Type that is assisting me with the debugging.

#include <map>
#include <string>
#include <iostream>
#include <ostream>

struct Type
{
    Type()
    {
        std::cout << "Default construction\n";
    };

    explicit Type(std::string obj) : obj(std::move(obj))
    {
        std::cout << "Other construction\n";
    }
    ~Type() = default;

    Type(const Type& other) : obj{other.obj}
    {
        std::cout << "Copy construction\n";
    }

    Type(Type&& other) noexcept : obj{std::move(other.obj)}
    {
        std::cout << "Move constructor\n";
    }

    Type& operator=(const Type& other)
    {
        std::cout << "Copy assignment\n";
        if (this == &other)
            return *this;
        obj = other.obj;
        return *this;
    }

    Type& operator=(Type&& other) noexcept
    {
        std::cout << "Move assignment\n";
        if (this == &other)
            return *this;
        obj = std::move(other.obj);
        return *this;
    }

    friend std::ostream& operator<<(std::ostream& os, const Type& obj1)
    {
        return os << obj1.obj;
    }

    std::string obj;
};


std::map<std::string, Type> mVariables;

Type Get(const std::string& variableName)
{
    const auto variableIt = mVariables.find(variableName);
    if(variableIt==std::end(mVariables)) {
        throw std::runtime_error("Unknown variable requested.");
    }
    return variableIt->second;
}

int main()
{
    mVariables.emplace(std::make_pair("key", Type("value")));
    const auto value = Get("key");  
    std::cout << value;
    return 0;
}

The above example provides the following output, which raises a few questions about make_pair, but that is not a discussion for here. I guess my confusion is, what in this example prevent the copy elision occurring?

Other construction
Move constructor
Move constructor
Copy construction
value

Solution

  • Before C++171, the semantic of const auto value = Get("key"); is copy-initializing a temporary object from the returned expression variableIt->second, then copy-initializing value from the temporary object. So there are basically two copies/moves originally.

    The copy/move from the temporary object can be elided by directly constructing value from variableIt->second according to N3797 [class.copy] paragraph 31 bullet 3:

    • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move.

    This copy is guaranteed since C++17 by preventing the temporary object from being materialized in semantic. The new semantic of const auto value = Get("key"); becomes copy-initializing value from variableIt->second2.

    The copy from variableIt->second cannot be elided because it does not meet the requirement of copy elision appearing in a return statement, i.e. N3797 [class.copy] paragraph 31 bullet 13:

    • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

    This is reasonable because the lifetime of variableIt->second does not end and may be used in future, so value cannot be optimized as an alias to variableIt->second, thus a copy is necessary.


    1The only difference since C++17 is the guaranteed copy elision mentioned in the third paragraph. It is more straightaway (in my opinion) to begin the analysis from the semantic before C++17.

    2There are several rules combined to conclude this conclusion in C++17, and it is not important for this question, so I emit the quote of corresponding rules from the standard.

    3The wording of this rule is changed a little in C++17, while the rule is essentially the same.