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};
}
@sean-parent
Copy link

It isn't bad - the move library in ASL (adobe source libraries) used to do a similar thing. The one issue with doing it as a general solution is that we (meaning those who follow Elements of Programming) don't require that a default constructed object be swapable - this is a strengthening of those requirements. The requirements of a default constructed object is only that it be partially formed, here you are requiring that you can assign from it. In reality this typically isn't an issue but it feeds into the discussion of efficient basis and what it means for a value to be unspecified. I wrote a blog post on the requirements of move() here http://sean-parent.github.io/2014/05/30/about-move/.

@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