tjzel
tjzel

Reputation: 115

Utilize copy/move elision with a never again used reference

This has been bothering me for some time. Say we have a constructor that takes many parameters (copies or rvalues):

Object(A a, B b, C c, D d, ...);

When you need to use this constructor you can do it like this:

Object obj{{A params...}, {B params}, ...};

Which is great optimization-wise, since no copy and move constructors are called.

However, it can be hard to read at times. Also, perhaps A can be instantiated here but B and C will be passed as a copy. Therefore it makes sense to re-write it like this:

A a{A params...};
B b{B params...};
...
Object obj{a, b, ...}; // much nicer, but copy constructors are called

The problem is, with the latter approach, a copy constructor gets invoked for each parameter. This theoretically isn't a problem, since we can use std::move on temporary parameters:

A a{A params...};
B b{B params...};
...
Object obj{std::move(a), std::move(b), ...} // no copy!

But here, we get move constructors invoked.

Is there some way to enforce copy elision in such case - declare the variables but have the output like in the first case? It's seems like a super simple optimization compiler could do but even -O3 doesn't fix it.


EDIT:

After some trial and error I concluded this can be solved using template constructor with universal reference parameters:

#include <iostream>

struct A{
    A(){
        std::cout << "A - default\n";
    }
    
    A(A &&a){
        std::cout << "A - move\n";
    }
    
    A(A &a){
        std::cout << "A - copy\n";
    }
};

struct B{
    A a_;
    
    template <typename T>
    // Not sure if std::forward is needed here.
    explicit B(T &&a): a_(std::move(std::forward<T>(a))){
    }

    static B BCopyElision(){
        return B{A{}};
    }

    static B BCopy(){
        A a{};
        return B(std::move(a));
    }

    static B BMove(){
        A a{};
        return B{std::move(a)};
    }
    
};

int main()
{
    B::BCopyElision();
    // A - default;
    // A - move;
    
    B::BCopy();
    // A - default;
    // A - move;
    
    B::BMove();
    // A - default
    // A - move;

    return 0;
}

Is there a way to achieve this without (unnecessary) template constructors?

Upvotes: 0

Views: 65

Answers (0)

Related Questions