Skip to content

Commit a3aa1df

Browse files
committed
✨ Add safe_identity
Problem: - `std::identity` can cause dangling references. Solution: - Add `safe_identity` which safely moves from an rvalue-reference argument. Note: - I have had similar use cases, and written similar functions before, but I think never quite as succinctly as elucidated by Dr Walter Brown in https://www.youtube.com/watch?v=sIZUg9tN4sk. Thanks, Walter!
1 parent 346cfb8 commit a3aa1df

3 files changed

Lines changed: 112 additions & 0 deletions

File tree

docs/functional.adoc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ NOTE: https://wg21.link/P2714[P2714] added the ability (in C\\++26) to use a
1010
non-type template parameter for the bound function; this works for function
1111
pointers in C++17, and also for lambda expressions in C++20 and beyond.
1212

13+
=== `safe_identity`
14+
15+
`safe_identity` is a function object (of type `safe_identity_t`) similar
16+
in intent to
17+
https://en.cppreference.com/w/cpp/utility/functional/identity.html[`std::identity`].
18+
A call to `safe_identity` returns its argument unchanged. _However_, the vital
19+
difference with `safe_identity` is in the value category of what is returned.
20+
21+
[source,cpp]
22+
----
23+
int x;
24+
auto f() -> int;
25+
26+
decltype(auto) r1 = std::identity{}(x); // r1 has type int &
27+
decltype(auto) r2 = std::identity{}(f()); // r2 has type int && - dangling!
28+
29+
decltype(auto) r3 = stdx::safe_identity(x); // r3 has type int &
30+
decltype(auto) r4 = stdx::safe_identity(f()); // r4 has type int - no longer dangling
31+
----
32+
33+
NOTE: In standard usage, the type is `std::identity` and we must instantiate it
34+
to use it; in `stdx`, the type is `safe_identity_t` and `safe_identity` is a
35+
`constexpr inline` variable of that type.
36+
1337
=== `with_result_of`
1438

1539
`with_result_of` is a class that can be used for lazy evaluation.

include/stdx/functional.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,5 +187,13 @@ template <> struct unary_plus<void> {
187187
}
188188
};
189189

190+
constexpr inline struct safe_identity_t {
191+
using is_transparent = void;
192+
193+
template <typename T>
194+
constexpr auto operator()(T &&t) const -> decltype(auto) {
195+
return T{std::forward<T>(t)};
196+
}
197+
} safe_identity;
190198
} // namespace v1
191199
} // namespace stdx

test/functional.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
#include "detail/tuple_types.hpp"
2+
13
#include <stdx/functional.hpp>
24

35
#include <catch2/catch_test_macros.hpp>
46

57
#include <type_traits>
8+
#include <utility>
69

710
namespace {
811
template <typename T, typename = void>
@@ -28,3 +31,80 @@ TEST_CASE("unary_plus transparency", "[functional]") {
2831
TEST_CASE("unary_plus calls operator+", "[functional]") {
2932
STATIC_REQUIRE(stdx::unary_plus<>{}(S{}) == 17);
3033
}
34+
35+
TEST_CASE("safe_identity returns unchanged argument", "[functional]") {
36+
static_assert(stdx::safe_identity(17) == 17);
37+
static_assert(stdx::safe_identity(move_only{17}).value == 17);
38+
}
39+
40+
TEST_CASE("safe_identity (copy)", "[functional]") {
41+
counter::reset();
42+
counter c0{};
43+
[[maybe_unused]] auto c1 = stdx::safe_identity(c0);
44+
CHECK(counter::copies == 1);
45+
}
46+
47+
TEST_CASE("safe_identity (move)", "[functional]") {
48+
counter::reset();
49+
counter c0{};
50+
[[maybe_unused]] auto c1 = stdx::safe_identity(std::move(c0));
51+
CHECK(counter::copies == 0);
52+
CHECK(counter::moves == 1);
53+
}
54+
55+
TEST_CASE("safe_identity transparency", "[functional]") {
56+
STATIC_REQUIRE(detect_is_transparent<stdx::safe_identity_t>);
57+
}
58+
59+
namespace {
60+
template <typename T> auto declval() -> T;
61+
}
62+
63+
TEST_CASE("safe_identity value categories", "[functional]") {
64+
static_assert(
65+
std::is_same_v<decltype(stdx::safe_identity(declval<int>())), int>);
66+
static_assert(
67+
std::is_same_v<decltype(stdx::safe_identity(declval<int &>())), int &>);
68+
static_assert(
69+
std::is_same_v<decltype(stdx::safe_identity(declval<int &&>())), int>);
70+
}
71+
72+
TEST_CASE("safe_identity cvref categories", "[functional]") {
73+
static_assert(
74+
std::is_same_v<decltype(stdx::safe_identity(declval<int const &>())),
75+
int const &>);
76+
static_assert(
77+
std::is_same_v<decltype(stdx::safe_identity(declval<int volatile &>())),
78+
int volatile &>);
79+
static_assert(std::is_same_v<decltype(stdx::safe_identity(
80+
declval<int const volatile &>())),
81+
int const volatile &>);
82+
static_assert(
83+
std::is_same_v<decltype(stdx::safe_identity(declval<int const &&>())),
84+
int>);
85+
static_assert(
86+
std::is_same_v<
87+
decltype(stdx::safe_identity(declval<int volatile &&>())), int>);
88+
static_assert(std::is_same_v<decltype(stdx::safe_identity(
89+
declval<int const volatile &&>())),
90+
int>);
91+
}
92+
93+
#if __cplusplus >= 202002L
94+
namespace {
95+
auto f() -> int { return 42; }
96+
} // namespace
97+
98+
TEST_CASE("safe_identity vs std::identity", "[functional]") {
99+
int x{};
100+
101+
decltype(auto) r1 = std::identity{}(x);
102+
static_assert(std::is_same_v<decltype(r1), int &>);
103+
decltype(auto) r2 = std::identity{}(f());
104+
static_assert(std::is_same_v<decltype(r2), int &&>);
105+
decltype(auto) r3 = stdx::safe_identity(x);
106+
static_assert(std::is_same_v<decltype(r3), int &>);
107+
decltype(auto) r4 = stdx::safe_identity(f());
108+
static_assert(std::is_same_v<decltype(r4), int>);
109+
}
110+
#endif

0 commit comments

Comments
 (0)