4

I have a function

class A { <has copy and move constructors > };

void f(A &&a) { ... }

I want to call the function f with a variable x, but I want to run f on a copy of x, not x itself. Why can't I do something like this?

f(copy(x));

But instead must do

f(A(x));

Is there anything similar to the copy function I describe above?

Note: please don't tell me to add an overload f that does the copying, I want something that doesn't require an overload and is explicit (i.e. copy makes it clear to the reader that copying is done here)

tohava
  • 5,344
  • 1
  • 25
  • 47
  • You could implement your own `copy` without much effort. – Alan Stokes Mar 11 '15 at 17:15
  • I know you wanted not to be told to add an overload, but I have a hard time seeing why you would want this particular implementation. – rlbond Mar 11 '15 at 17:18
  • @rlbond - This implementation allows a person reading the function call to understand whether a copy is created or not, the overload does not. – tohava Mar 11 '15 at 17:20
  • 4
    I don't get it... why don't you simply change `void f(A &&a)` to `void f(A a)`? – fredoverflow Mar 11 '15 at 17:22
  • 1
    Is is necessary that a copy happens? Or would something like `template T const& as_const(T const& x) { return x; }` be sufficient? – dyp Mar 11 '15 at 17:22
  • @dyp - The function modifies its arguments, so yes, a copy is needed (I want to avoid losing the original) – tohava Mar 11 '15 at 17:24
  • `f(A{x})` makes it explicit to the caller that a copy is happening. `f(copy(x))` doesn't make it explicit, I have to go an look for the function `copy`, and then I get confused and wonder why you didn't just write `f(A{x})`. – Jonathan Wakely Mar 15 '15 at 14:16
  • @JonathanWakely - What if `A` is `std::vector > > >` – tohava Mar 15 '15 at 14:26
  • There's this neat thing called `typedef`. If you're using that type without giving it a better name you already have problems with your code. – Jonathan Wakely Mar 15 '15 at 14:36
  • @JonathanWakely - I do not agree. In `java`, if I write `x.clone()`, there is no need to write the type of `x` all over again. There is no need to clutter things with an extra `typedef`. – tohava Mar 15 '15 at 14:40
  • Isn't `clone()` widely regarded as broken? Anyway, as I already said, good practice would be to have such a typedef already, so it wouldn't be an _extra_ typedef. Are you telling me you would really have an API defined in terms of a type like `vector>>` rather than a more descriptive typedef? You might want to reconsider that. – Jonathan Wakely Mar 15 '15 at 16:22
  • 1
    @Jonathan: Given that `copy(x)` is a function named *copy*, you should probably explain why you think it's unclear whether it would copy something. –  Mar 15 '15 at 16:57
  • @Hurkyl, because without checking the definitions (or documentation) I don't know if it does something _else_ as well (like register the object somewhere, or log the copy, or increment a counter of copies). Or maybe by analogy with `std::move` it doesn't copy at all, it just casts something to an lvalue! If the `copy` function is used throughout the codebase and is a local convention, that would be OK, it's just a local idiom to be learnt. If it's a one-off function used in one place just to create a copy to pass to a function, I would wonder why it wasn't simply `f(A{x})`. – Jonathan Wakely Mar 15 '15 at 17:40

4 Answers4

8

What about this?

template <typename T>
T copy(T t) {return t;}

The copy is done when you pass t by value and a following move take place.

http://coliru.stacked-crooked.com/a/cbc1a161f65a022b


If you can modify your function to:

void func(A) {}

then you can do

int main() {
   A a;
   func(a);            //copies
   func(std::move(a)); //moves
   return 0;
}

Without any extra constructor call.

http://coliru.stacked-crooked.com/a/1bc80bcce64f242f

DarioP
  • 5,377
  • 1
  • 33
  • 52
  • 2
    And a bonus copy on the return :-) (Which admittedly is almost certainly elided by the compiler.) – Alan Stokes Mar 11 '15 at 17:18
  • There's nothing in the standard library to do this? – tohava Mar 11 '15 at 17:19
  • 7
    @AlanStokes It cannot be elided, since the source is a function parameter. It will be a move, though. – dyp Mar 11 '15 at 17:20
  • Here we go: http://coliru.stacked-crooked.com/a/f39b62487f4930d8 this is a +1 for @dyp – DarioP Mar 11 '15 at 17:28
  • @Freddy There are no atomic variables here. But you are correct, as is dyp. (Assuming T has a move constructor, which we are told the original A does.) – Alan Stokes Mar 11 '15 at 17:30
  • 2
    A variant like `template{}>* = nullptr> T copy(T&& t) { return t; }` would eliminate the additional move. I've chosen to restrict this to lvalues since I think it typically does not make sense for rvalues, and if it does, it might benefit from a more explicit `copy_rvalue_yes_really` function. – dyp Mar 11 '15 at 17:49
  • @dyp Why not `template std::remove_cv_t copy(T&t){return t;}`? – Yakk - Adam Nevraumont Mar 11 '15 at 18:19
  • @Yakk Hmm that can still bind to rvalues, although only to const rvalues. – dyp Mar 11 '15 at 19:19
5
template<class T>
std::remove_cv_t<T> copy(T& t) {
  return t;
}
template<class T>
void copy(T&&)=delete; // block rvalue copy
template<class T, size_t N>
void copy(T(&)[N]) = delete; // we can implement this if we want: see below

will copy any lvalue. On rvalues and non-copyable types (like arrays) it will fail to match or compile.

template<class T>
std::decay_t<T> copy_even_if_rvalue(T&& t) {
  return std::forward<T>(t);
}
namespace details {
  template<class T, size_t N, size_t...Is>
  std::array<T, N> copy_even_if_rvalue(T(&arr)[N], std::index_sequence<Is...>)
  {
    return {{arr[Is]...}};
  }
}
template<class T, size_t N>
std::array<T, N> copy_even_if_rvalue(T(&arr)[N]) {
  return details::copy_even_if_rvalue(arr, std::make_index_sequence<N>{} );
}
template<class T, size_t N>
std::array<T, N> copy_even_if_rvalue(T(&&arr)[N]) {
  return copy_even_if_rvalue(arr); // forward to lvalue version
}

will copy both rvalues and lvalues. Usually copy is a smarter operation than copy_even_if_rvalue in my opinion.

copy_even_if_rvalue will convert raw C arrays into std::arrays, because that is about the only sensible way to copy them. An =delete overload would be the other reasonable option.

tohava
  • 5,344
  • 1
  • 25
  • 47
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Ok, this doesn't work, I get an ambiguity between the `T &` and the `T &&` version. – tohava Mar 15 '15 at 14:10
  • @tohava compiler and version? Argument? There shoukd be a pref for `T&` over `T&&`, but maybe I missed something. Or are you talking about the `const` edit version you did? – Yakk - Adam Nevraumont Mar 15 '15 at 17:10
  • I tried both with and without const. I am using gcc 4.8.2, and i am sending a variable that appears in a mutable lambda closure. – tohava Mar 15 '15 at 17:45
  • @tohava Both [clang](http://coliru.stacked-crooked.com/a/c8ef3dc1bdf3b7b1) and [gcc](http://ideone.com/kYss22) seem to compile simple test versions fine. An actual link to simplified code that isn't working would help me work out if I did a mistake, or if your compiler is non conforming, and/or if there is an easy fix. – Yakk - Adam Nevraumont Mar 16 '15 at 00:36
0

The shortest way is simply f(A{x})

If for some reason the type A is too long or ugly to type out, then this is still shorter than defining a new function at global scope:

f(std::decay_t<decltype(x)>(x));

Or without the C++14 alias template:

f(typename std::decay<decltype(x)>::type(x));
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
-3

what about using copy constructor? You can do something like:

A::A(const A &obj){
    //copy constructor here
}

// in external header
void copy(const A &obj){
    return A::A(obj);
}

Have fun

gf

EDIT after suggestions

jetstream
  • 106
  • 1
  • 10
  • -1 - I said I want to avoid having to write the type again, and I also want something generic enough to avoid adding a member function to each class. – tohava Mar 11 '15 at 17:21
  • "Note: please don't tell me to add an overload f that does the copying." Overloading=same name, different args. Read manual please. No overload in my answer and no request to not to add a member function. You can as well add my solution via an external header. – jetstream Mar 11 '15 at 17:37
  • I agree this doesn't add an overload of `f` ... but it doesn't work either. You might want to actually return something from the `copy` member function. And why not just `A A::copy() const { return *this; }` ? – Jonathan Wakely Mar 15 '15 at 16:06
  • You're right. my real answer (correct one) is: `A A::copy(const A &obj){return A::A(obj);}` If you simply return a pointer to the this automatic pointer, the old values will not be cloned, but, instead, the pointer would be cloned. You'll have two pointers pointing at the same memory location, without actually copying the object pointed. Invoking the copy constructor you create a clone of the original object. – jetstream Mar 16 '15 at 16:54