Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inferring types whilst eliding moves/copies without a macro

Consider the following simple make_pair class:

template <class X, class Y>
struct Pair
{
    X x;
    Y y;
};

Also, we'll make a simple class to show any moves/copies:

struct C
{
    C(int n_) : n(n_) {};
    C(const C& x) { n = x.n; std::cout << "Copy: " << n << std::endl; }
    C(C&& x)      { n = x.n; std::cout << "Move: " << n << std::endl; }
    int n;
};

We can then run:

auto z1 = Pair<C, C>{C(1),C(2)};

And there is no output, C is not moved or copied.

However, we have to specify the types in the constructor Pair. Lets say we want to infer these. We could do something like this:

template <class X, class Y>
Pair<X, Y> make_pair(X&& x, Y&& y)
{
    return Pair<X, Y>{std::forward<X>(x), std::forward<Y>(y)};
}

And then we can do:

auto z2 = make_pair(C(3),C(4));

But this prints:

Move: 3
Move: 4

Not a problem if C is a heap allocated type, but if a stack allocated type, a move is basically a copy.

But then lets define this macro:

#define MAKE_PAIR(x,y) decltype(make_pair(x,y)){x,y}

Then we can do:

auto z3 = MAKE_PAIR(C(5),C(6));

And this does type deduction AND doesn't need a move. But we need to make a macro, which I feel is a bit messy, and also stops us from using operators to do this sort of thing.

Is there a solution that does the following:

(1) Deduces types (like 2 and 3)
(2) Doesn't require a copy or move (like 1 and 3)
(3) Doesn't require a macro (like 1 and 2)

The best I can get is two out of three, but surely three out of three is possible? I can't imagine C++ would force one to use macros to get the behaviour I was after, as apparently C++ is moving away from macros.

The code is here.

like image 280
Clinton Avatar asked Nov 02 '12 00:11

Clinton


1 Answers

I can't imagine C++ would force one to use macros to get the behaviour I was after, as apparently C++ is moving away from macros.

The behavior you're after was never guaranteed by the standard in the first place. Elision is an optimization; it is not required for any implementation. So none of them are guaranteed to do what you want, though obviously some of them at least allow it to be possible.

Forwarding effectively makes elision impossible; there's nothing that can be done about that fact. Perfect forwarding is all about references and reference collapsing; elision is about values initializing value parameters, which it can't know about at the initial call site.

In real-world situations, this should not be an issue. Most of the things that are actually worth eliding are things where copying is expensive. Copying a few ints or floats, especially for a trivial class, will likely not even show up as a blip on a profiler. In the vast majority of cases, objects that are expensive to copy are so because they hold some kind of resource, such as allocated memory. So most types that are expensive to copy can also be moveable, and thus will move cheaply.

In any case, yes, if you want to have the possibility of elision, you cannot use forwarding.

like image 62
Nicol Bolas Avatar answered Oct 06 '22 00:10

Nicol Bolas