diff --git a/include/stdx/type_traits.hpp b/include/stdx/type_traits.hpp index 350296d..26a9094 100644 --- a/include/stdx/type_traits.hpp +++ b/include/stdx/type_traits.hpp @@ -244,22 +244,71 @@ CONSTEVAL auto shrink() { } template -concept is_shrunk = requires(T t) { +concept is_shrunk = requires(T const &t) { { t()() } -> is_shrinkwrapped; }; template CONSTEVAL auto maybe_expand() -> T; template CONSTEVAL auto maybe_expand() -> typename decltype(T{}()())::type; + +template constexpr auto maybe_expand(T &&t) -> decltype(auto) { + return T(std::forward(t)); +} +template +constexpr auto maybe_expand(T &&) -> + typename decltype(std::remove_cvref_t{}()())::type { + using R = typename decltype(std::remove_cvref_t{}()())::type; + static_assert(std::is_default_constructible_v, + "expand(T) cannot default-construct the return value: maybe " + "use expand_t instead?"); + static_assert( + std::is_empty_v, + "Danger! expand(T) would return a default-constructed but non-empty " + "object: use expand_t{} instead if this is what you want"); + return R{}; +} } // namespace detail -template CONSTEVAL auto shrink() -> decltype(detail::shrink()); +template CONSTEVAL auto shrink() -> decltype(detail::shrink()) { + static_assert( + always_false_v, + "shrink() should not be called outside an unevaluated context"); +} +template +CONSTEVAL auto shrink(T const &) -> decltype(detail::shrink()) { + return {}; +} template -CONSTEVAL auto expand() -> decltype(detail::maybe_expand()); +CONSTEVAL auto expand() -> decltype(detail::maybe_expand()) { + using R = decltype(detail::maybe_expand()); + static_assert( + always_false_v, + "expand() should not be called outside an unevaluated context"); +} +template constexpr auto expand(T &&t) -> decltype(auto) { + return detail::maybe_expand(std::forward(t)); +} + #else -template CONSTEVAL auto shrink() -> T; -template CONSTEVAL auto expand() -> T; +template CONSTEVAL auto shrink() -> T { + static_assert( + always_false_v, + "shrink() should not be called outside an unevaluated context"); +} +template constexpr auto shrink(T &&t) -> decltype(auto) { + return T(std::forward(t)); +} + +template CONSTEVAL auto expand() -> T { + static_assert( + always_false_v, + "expand() should not be called outside an unevaluated context"); +} +template constexpr auto expand(T &&t) -> decltype(auto) { + return T(std::forward(t)); +} #endif template using shrink_t = decltype(shrink()); diff --git a/test/type_traits.cpp b/test/type_traits.cpp index 026210b..a38db4e 100644 --- a/test/type_traits.cpp +++ b/test/type_traits.cpp @@ -245,21 +245,32 @@ TEST_CASE("non-structural types", "[type_traits]") { STATIC_REQUIRE(not stdx::is_structural_v); } -#if __cplusplus >= 202002L namespace { template struct long_type_name {}; +using A = long_type_name; +using B = long_type_name; +using C = long_type_name; } // namespace -TEST_CASE("type shrinkage", "[type_traits]") { - using A = long_type_name; - using B = long_type_name; - using C = long_type_name; +TEST_CASE("type shrinkage (by type)", "[type_traits]") { using X = stdx::shrink_t; +#if __cplusplus >= 202002L STATIC_CHECK(stdx::type_as_string().size() < stdx::type_as_string().size()); - STATIC_CHECK(std::same_as, C>); +#endif + STATIC_CHECK(std::is_same_v, C>); } + +TEST_CASE("type shrinkage (by value)", "[type_traits]") { + auto c = C{}; + auto x = stdx::shrink(c); + auto y = stdx::expand(x); +#if __cplusplus >= 202002L + STATIC_CHECK(stdx::type_as_string().size() < + stdx::type_as_string().size()); #endif + STATIC_CHECK(std::is_same_v); +} TEST_CASE("nth type in pack", "[type_traits]") { STATIC_REQUIRE(