Search code examples
c++c++17in-placecopy-elision

C++ copy elision of fields


I am trying to get copy elision to work for fields of the object that is to be returned.

Example code:

#include <iostream>

struct A {
    bool x;
    A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
    B(const A &a) : a(a) {
        std::cout << "B constructed" << std::endl;
    }
    B(const B &other) : a(other.a) {
        std::cout << "B copied" << std::endl;
    }
    B(B &&other) : a(other.a) {
        std::cout << "B moved" << std::endl;
    }
    B &operator=(const B &other) {
        std::cout << "B reassigned" << std::endl;
        if (this != &other) {
            a = other.a;
        }
        return *this;
    }
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}

I compile with: g++ -std=c++17 test.cpp -o test.exe

output:

A constructed
A copied
B constructed
1

B is constructed in-place. Why is A not? I would at least expect it to be move-constructed, but it is copied instead.

Is there a way to also construct A in-place, inside the B to be returned? How?


Solution

  • I have figured how to do what I wanted.

    The intent was to return multiple values from a function with the minimal amount of "work".

    I try to avoid passing return values as writable references (to avoid value mutation and assignment operators), so I wanted to do this by wrapping the objects to be returned in a struct.

    I have succeeded at this before, so I was surprised that the code above didn't work.

    This does work:

    #include <iostream>
    
    struct A {
        bool x;
        explicit A(bool x) : x(x) {
            std::cout << "A constructed" << std::endl;
        }
        A(const A &other) : x(other.x) {
            std::cout << "A copied" << std::endl;
        }
        A(A &&other) : x(other.x) {
            std::cout << "A moved" << std::endl;
        }
        A &operator=(const A &other) {
            std::cout << "A reassigned" << std::endl;
            if (this != &other) {
                x = other.x;
            }
            return *this;
        }
    };
    
    struct B {
        A a;
    };
    
    B foo() {
        return B{A{true}};
    }
    
    
    int main() {
        B b = foo();
        std::cout << b.a.x << std::endl;
    }
    

    output:

    A constructed
    1
    

    The key was to remove all the constructors of B. This enabled aggregate initialization, which seems to construct the field in-place. As a result, copying A is avoided. I am not sure if this is considered copy elision, technically.