diff --git a/docs/functional.adoc b/docs/functional.adoc index db56581..e6e5cc5 100644 --- a/docs/functional.adoc +++ b/docs/functional.adoc @@ -10,6 +10,30 @@ NOTE: https://wg21.link/P2714[P2714] added the ability (in C\\++26) to use a non-type template parameter for the bound function; this works for function pointers in C++17, and also for lambda expressions in C++20 and beyond. +=== `safe_identity` + +`safe_identity` is a function object (of type `safe_identity_t`) similar +in intent to +https://en.cppreference.com/w/cpp/utility/functional/identity.html[`std::identity`]. +A call to `safe_identity` returns its argument unchanged. _However_, the vital +difference with `safe_identity` is in the value category of what is returned. + +[source,cpp] +---- +int x; +auto f() -> int; + +decltype(auto) r1 = std::identity{}(x); // r1 has type int & +decltype(auto) r2 = std::identity{}(f()); // r2 has type int && - dangling! + +decltype(auto) r3 = stdx::safe_identity(x); // r3 has type int & +decltype(auto) r4 = stdx::safe_identity(f()); // r4 has type int - no longer dangling +---- + +NOTE: In standard usage, the type is `std::identity` and we must instantiate it +to use it; in `stdx`, the type is `safe_identity_t` and `safe_identity` is a +`constexpr inline` variable of that type. + === `with_result_of` `with_result_of` is a class that can be used for lazy evaluation. diff --git a/include/stdx/functional.hpp b/include/stdx/functional.hpp index 5497af9..1d5da4f 100644 --- a/include/stdx/functional.hpp +++ b/include/stdx/functional.hpp @@ -187,5 +187,13 @@ template <> struct unary_plus { } }; +constexpr inline struct safe_identity_t { + using is_transparent = void; + + template + constexpr auto operator()(T &&t) const -> decltype(auto) { + return T(std::forward(t)); + } +} safe_identity; } // namespace v1 } // namespace stdx diff --git a/test/functional.cpp b/test/functional.cpp index c013f3c..9b728f6 100644 --- a/test/functional.cpp +++ b/test/functional.cpp @@ -1,8 +1,11 @@ +#include "detail/tuple_types.hpp" + #include #include #include +#include namespace { template @@ -28,3 +31,61 @@ TEST_CASE("unary_plus transparency", "[functional]") { TEST_CASE("unary_plus calls operator+", "[functional]") { STATIC_REQUIRE(stdx::unary_plus<>{}(S{}) == 17); } + +TEST_CASE("safe_identity returns unchanged argument", "[functional]") { + static_assert(stdx::safe_identity(17) == 17); + static_assert(stdx::safe_identity(move_only{17}).value == 17); +} + +TEST_CASE("safe_identity (copy)", "[functional]") { + counter::reset(); + counter c0{}; + [[maybe_unused]] auto c1 = stdx::safe_identity(c0); + CHECK(counter::copies == 1); +} + +TEST_CASE("safe_identity (move)", "[functional]") { + counter::reset(); + counter c0{}; + [[maybe_unused]] auto c1 = stdx::safe_identity(std::move(c0)); + CHECK(counter::copies == 0); + CHECK(counter::moves == 1); +} + +TEST_CASE("safe_identity transparency", "[functional]") { + STATIC_REQUIRE(detect_is_transparent); +} + +namespace { +template auto declval() -> T; +} + +TEST_CASE("safe_identity value categories", "[functional]") { + static_assert( + std::is_same_v())), int>); + static_assert( + std::is_same_v())), int &>); + static_assert( + std::is_same_v())), int>); +} + +TEST_CASE("safe_identity cvref categories", "[functional]") { + static_assert( + std::is_same_v())), + int const &>); + static_assert( + std::is_same_v())), + int volatile &>); + static_assert(std::is_same_v())), + int const volatile &>); + static_assert( + std::is_same_v())), + int>); + static_assert( + std::is_same_v< + decltype(stdx::safe_identity(declval())), int>); + static_assert(std::is_same_v())), + int>); +}