Search code examples
c++move

When initialising member with temporary, how to ensure a single call to temporary's constructor?


Suppose I have a wrapper:

template<typename T>
struct Outer {
    T inner;
    ...
};

, and I want to create an Outer wrapping an Inner, like so:

Outer<Inner> wrapper(Inner(...)); // Inner object is a temporary

Is it possible to declare Outer/Inner such that creating an Outer object from a temporary Inner involves the construction of only a single Inner object? I tried declaring the move constructor for Inner, and having Outer take an rvalue reference to Inner, and even explicitly calling std::move on the temporary, but still, 2 copies of Inner are created:

#include <stdio.h>
#include <utility>

template<typename T>
struct Outer {
    T inner;
    Outer(T&& inner) : inner(inner) {}
};

struct Inner {
    int x;
    int y;

    Inner(const int x, const int y) : x(x), y(y) { printf("ctor\n"); }
    Inner(const Inner& rhs) : x(rhs.x), y(rhs.y) { printf("copy ctor\n"); }
    Inner(Inner&& rhs) : x(std::exchange(rhs.x, 0)), y(std::exchange(rhs.y, 0)) {
        printf("move ctor\n");
    }
    Inner& operator=(const Inner& rhs) { 
        printf("assign\n"); 
        return *this = Inner(rhs); 
    }
    Inner& operator=(Inner&& rhs) { 
        printf("move assign\n");
        std::swap(x, rhs.x);
        std::swap(y, rhs.y);
        return *this;
    }
    ~Inner() { printf("dtor\n"); }
};

int main() {
    Outer<Inner> wrapper(Inner(123, 234));
    // SAME: Outer<Inner> wrapper(std::move(Inner(123, 234)));
    return 0;
}

Output:

ctor <-- The temporary
copy ctor <-- Yet another instance, from the temporary
dtor
dtor

As shown above, the creation of an Outer instance creates 2 Inners. This seems strange as I thought the temporary can be elided, or at least, invoke the move constructor. Is it possible to instantiate Outer with a single Inner construction?


Solution

  • I guess you are looking for this:

    #include <stdio.h>
    #include <utility>
    
    template <typename T> struct Outer {
        T inner;
        // Outer(T &&inner) : inner(std::move(inner)) {}
        template <class... Args> Outer(Args &&...args) :inner(args...) {}
    };
    
    struct Inner {
        int x;
        int y;
    
        Inner(const int x, const int y) : x(x), y(y) { printf("ctor\n"); }
        Inner(const Inner &rhs) : x(rhs.x), y(rhs.y) { printf("copy ctor\n"); }
        Inner(Inner &&rhs)
            : x(std::exchange(rhs.x, 0)), y(std::exchange(rhs.y, 0)) {
            printf("move ctor\n");
        }
        Inner &operator=(const Inner &rhs) {
            printf("assign\n");
            return *this = Inner(rhs);
        }
        Inner &operator=(Inner &&rhs) {
            printf("move assign\n");
            std::swap(x, rhs.x);
            std::swap(y, rhs.y);
            return *this;
        }
        ~Inner() { printf("dtor\n"); }
    };
    
    int main() {
        Outer<Inner> wrapper(123, 234);
        // SAME: Outer<Inner> wrapper(std::move(Inner(123, 234)));
        return 0;
    }