Re: C++11 perfect identity function
On Monday, September 1, 2014 4:30:03 AM UTC+2, raskolnikov wrote:
I sometimes have to write identity functions, right? Or functions that
do something to its parameter and then want to return it with the same
value category that they received it. What is the right C++11 way?
Depends on what it should do. There is probably no way to write such a
function that does the right thing in every case. But since you use the
word "perfect" I'm assuming that you are not interested in copying or
moving something.
Option 1:
template <typename T>
T&& identity(T&& x) { return x; }
Won't compile if you invoke it with an rvalue because an rvalue
reference cannot be initialized to refer to an lvalue like x.
Option 2:
template <typename T>
T&& identity(T&& x)
{ return std::forward<T>(x); }
This fixes the compilation error for rvalues. But now you have created
a function that might give you bad surprizes:
for (auto x: identity(get_vector_by_value())) {
cout << x << endl;
}
will invoke undefined behaviour because the temporary vector won't
outlive the reference returned by identity. The same is true for
std::move and std::forward by the way.
Option 3:
template <typename T>
auto identity(T&& x)
-> decltype(std::forward<T>(x))
{ return std::forward<T>(x); }
This is effectivly the same as option 2 since
decltype(std::forward<T>(x)) is equivalent to T&& in this case.
I now use Option 3 because it seems immediatelly correct, but it seems to
me that the other simpler ones might be correct too. What do you think?
As I said, it depends on what you are trying to achieve. The problem
with functions returning references is that you have to make sure
these references stay valid long enough. The lifetime extension rule
that makes this work
const int& foo = 23; // OK (search "most important const")
does not apply to the following case anymore
const int& foo = identity(23); // dangling reference
PS. Extra points: and in C++14?
If you want to turn option 2/3 into C++14 code, you could write
template <typename T>
decltype(auto) identity(T&& x)
{ return std::forward<T>(x); }
If you don't want the function to return references but values, you
would instead write
template <typename T>
auto identity(T&& x)
{ return std::forward<T>(x); }
This is just like auto and decltype locally:
int&& foo = ...;
auto x = foo; // x is of type int
decltype(auto) y = foo; // y is of type int&&
where decltype(auto) refers to the _declared type_ of foo and is
hance an rvalue reference. I'd try to avoid overusing this, however
because it's easier to create dangling references this way.
And having played with the Rust language for a while, I have to
mention that creating dangling references in Rust is _much_ harder
thanks to lifetimes being part of the type system and the compiler's
so-called "borrow checker" that makes sure that references don't
outlive their pointees. Basically, it's impossible unless you hit
a compiler bug or circumvent the typesystem in code blocks that are
explicitly marked "unsafe".
Cheers!
sg
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]