Search code examples
c++rvonrvo

Can I rely on named return value optimisation for complicated return types?


Consider something like this:

typedef std::unordered_multiset<int> Set;
typedef std::set<Set> SetOfSets;

SetOfSets somethingRecursive(SomeType somethingToAnalyze) {
    Set s;
    // ...
    // check base cases, reduce somethingToAnalyze, fill in s
    // ...
    SetOfSets ss = somethingRecursive(somethingToAnalyze);
    ss.insert(s);
    return ss;
}

This approach is fairly standard for problems like generating subsets, permutations, etc. However, I tried making a diagram of what exactly Return Value Optimization should optimize here given the fairly complex internal data structure of the type (std::unordered_multiset is a hash table and std::set is 'typically' a binary search tree) and, well, I can only hope that compilers are smarter than me.

So, talking performance and (in case it matters) C++14, can I return a SetOfSets here or should I just pass it by reference as an out parameter?


Solution

  • Before C++17, you cannot rely on copy elision at all, since it is optional. However, all mainstream compilers will very likely apply it (e.g., GCC applies it even with -O0 optimization flag, you need to explicitly disable copy elision by -fno-elide-constructors if you want to).

    However, std::set supports move semantics, so even without NRVO, your code would be fine.

    Note that in C++17, NRVO is optional as well. RVO is mandatory.


    To be technically correct, IMO, there is no RVO in C++17, since when prvalue is returned, no temporary is materialized to be moved/copied from. The rules are kind of different, but the effect is more or less the same. Or, even stronger, since there is no need for copy/move constructor to return prvalue by value in C++17:

    #include <atomic>
    
    std::atomic<int> f() {
      return std::atomic<int>{0};
    }
    
    int main() {
      std::atomic<int> i = f();
    }
    

    In C++14, this code does not compile.