Skip to content

Instantly share code, notes, and snippets.

@mpark
Last active August 29, 2015 14:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpark/2955020279470d2da354 to your computer and use it in GitHub Desktop.
Save mpark/2955020279470d2da354 to your computer and use it in GitHub Desktop.
A discussion with @sean-parent regarding passing an sink arguments of arbitrary type T by value.
/**
* If we know that the sink argument is movable,
* then passing by value costs nothing if an rvalue is passed and
* only an extra move if an lvalue is passed.
* I agree that the extra move is no big deal here since it should be
* a constant time operation in most cases.
*
* So I like use cases such as:
*
* class TSink {
* public:
*
* TSink(std::string text) : Text(std::move(text)) {}
*
* private:
*
* std::string Text;
*
* }; // TSink
*
* However, if the sink argument is not movable,
* then passing by value costs nothing if an rvalue is passed but
* an extra __copy__ if an lvalue is passed.
* I'm not sure if an extra copy is acceptable?
*
* So my question is, do we still want this pattern for arbitrary Ts which
* may not be movable and therefore incur an extra copy rather than an extra move?
*
* // Copies twice if TVal is not movable on the way into the function, and
* // a move operation which falls back to copy.
* template <typename TVal>
* class TSink {
* public:
*
* TSink(TVal val) : Val(std::move(val)) {}
*
* private:
*
* TVal Val;
*
* }; // TSink<TVal>
*
* Note. This is what `model` looks like at: https://t.co/l1fnqjaOhD
**/
#include <iostream>
// Non-movable object.
class TObj {
public:
TObj(int x) : X(x) {
std::cout << "TObj(" << X << ")" << std::endl;
}
TObj(const TObj &that) : X(that.X) {
std::cout << "TObj(const TObj &): " << X << std::endl;
}
~TObj() {
std::cout << "~TObj(" << X << ")" << std::endl;
}
int X;
}; // TObj
template <typename TVal>
class TSinkByValue {
public:
TSinkByValue(TVal val) : Val(std::move(val)) {}
private:
TVal Val;
}; // TSinkByValue
template <typename TVal>
class TSinkByForward {
public:
template <typename TForwardVal>
TSinkByForward(TForwardVal &&val)
: Val(std::forward<TForwardVal>(val)) {}
private:
TVal Val;
}; // TSinkByForward
int main() {
std::cout << "Sink by value, Rvalue." << std::endl;
TSinkByValue<TObj>{TObj{42}};
std::cout << "Sink by forward, Rvalue." << std::endl;
TSinkByForward<TObj>{TObj{42}};
std::cout << "Construct a TObj." << std::endl;
TObj obj(42);
std::cout << "Sink by value, Lvalue." << std::endl;
TSinkByValue<TObj>{obj};
std::cout << "Sink by forward, Lvalue." << std::endl;
TSinkByForward<TObj>{obj};
}
@mpark
Copy link
Author

mpark commented Jun 5, 2014

Right, I did consider the fact that it enforces requirements on the types. I thought practically speaking perhaps they are okay. I shall read Elements of Programming in the near future.

I have indeed been following the discussion regarding destructive move and your blog post in specific. In general I share your opinion that we should first focus on utilizing other forms of hardware.

Thanks for the discussion Sean, I really appreciate your feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment