1

I have a library function (not under my control here) which take an r value reference to the movable and copyable type Bar:

void foo(Bar&& b);

In my own code, I sometimes need to give it a copy of an existing value, such as

const auto b = getBar();
foo(mk_copy(b));
b.baz();

This is what comes to mind,

template<typename T> auto mk_copy(T val){return val;}

Is there a standard way to do this, or a more common pattern? Perhaps even

template<typename T> auto mk_copy(T&& val){return std::forward<T>(val);}

Or, as pscill points out just writing the name of the class again,

foo(Bar(b));

but I prefer not to repeat type names.

Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • 1
    How about just calling the copy constructor? `foo(Bar{b});` – pschill Feb 20 '17 at 16:15
  • 1
    `make_copy` does seem the cleanest. The prefix `make` seems to be on its way to become a convention for this kind of thing. – Cheers and hth. - Alf Feb 20 '17 at 16:29
  • Why can't you just write an overload of `foo` for lvalues in the same namespace as the real library function (which internally copies and moves to the real func) so that calling code can just look perfectly natural without the `mk_copy` noise? – ildjarn Feb 20 '17 at 17:28
  • 2
    This is essentially `decay_copy`, which we may get one day - or not. That paper seems to have been busy falling through the cracks. – T.C. Feb 20 '17 at 19:24
  • @T.C. Cool, thank you. – Johan Lundberg Feb 20 '17 at 21:39

3 Answers3

1

There is no standard mechanism, but if you want one, your examples aren't very good. The first mk_copy copies T twice (or copies and moves). The second one seems very confusing as to what it's trying to do.

The obvious way is to simply take a const T&, like you normally would:

template<typename T> T mk_copy(const T &val){return val;}
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Isn't RVO applied for the 1st one? – songyuanyao Feb 20 '17 at 16:33
  • 2
    @songyuanyao: No. [Elision does not apply to function parameters](http://stackoverflow.com/questions/6009278/why-are-by-value-parameters-excluded-from-nrvo/9595610#9595610). – Nicol Bolas Feb 20 '17 at 16:38
  • Thanks. The first, does a copy and a move no? The second one is supposed to make a single copy, but optimize to a single move if possible. A bit of overkill but is it wrong? I admit I didn't think it through. – Johan Lundberg Feb 20 '17 at 16:38
  • @JohanLundberg: A copy followed by a copy/move, depending on what `T` can do. – Nicol Bolas Feb 20 '17 at 16:54
  • Revisited: "The second one seems very confusing as to what it's trying to do". As T.C. points out this is also the same (minus some conditionals ) as decay_copy, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3255.html. – Johan Lundberg Feb 21 '17 at 07:46
1

For the built-in types the prefix + plays the role of “make a copy”:

void foo( char&& ) {}
void bar( double*&& ) {}

auto main() -> int
{
    char x{};
    foo( +x );

    double* p{};
    bar( +p );
}

Still, it would probably be confusing to readers of the code if you applied prefix + to some other type, and a general prefix operator+ template might end up in conflict with some class' own prefix operator+.

So, I suggest using the now apparent naming convention for makers, namely a make prefix.

Then it can look like this:

template< class Type >
auto make_copy( Type const& o ) -> Type { return o; }

Your first proposed solution,

template<typename T> auto mk_copy(T val){return val;}

suffers from potentially copying the value twice: first copying to the by-value argument, and then copying to the function result.

This is not a problem for a movable type such as a standard library container, because the return type copying will reduce to a move. But it can be a problem for largish non-movable type, as can occur in legacy code.


The second proposed solution,

template<typename T> auto mk_copy(T&& val){return std::forward<T>(val);}

takes the argument by forwarding reference (a.k.a. universal reference), and then deduces the return type from a re-creation of the argument type. The return type will always be a non-reference, since that's what plain auto deduces, so this is technically correct. But it's needlessly complicated.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
1

I think you'd better just define that mk_copy method, or add one more declaration:

auto b_copy = b;
foo(std::move(b_copy));

These are clearer to the readers.


Just to demonstrate the possibilities, you could use decltype to get the type:

foo(std::decay_t<decltype(b)>{b});

or you could get the same effect with lambdas:

foo([&]{return b;}());

or tuples:

foo(std::get<0>(std::make_tuple(b)));

or pairs:

foo(std::make_pair(b, 0).first);
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005