diff --git a/CMakeLists.txt b/CMakeLists.txt index e28ffa6..257baae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,8 +62,8 @@ target_sources( include/stdx/detail/list_common.hpp include/stdx/env.hpp include/stdx/for_each_n_args.hpp - include/stdx/functional.hpp include/stdx/function_traits.hpp + include/stdx/functional.hpp include/stdx/intrusive_forward_list.hpp include/stdx/intrusive_list.hpp include/stdx/iterator.hpp @@ -72,14 +72,15 @@ target_sources( include/stdx/numeric.hpp include/stdx/optional.hpp include/stdx/panic.hpp + include/stdx/pp_map.hpp include/stdx/priority.hpp include/stdx/ranges.hpp include/stdx/rollover.hpp include/stdx/span.hpp include/stdx/static_assert.hpp + include/stdx/tuple.hpp include/stdx/tuple_algorithms.hpp include/stdx/tuple_destructure.hpp - include/stdx/tuple.hpp include/stdx/type_traits.hpp include/stdx/udls.hpp include/stdx/utility.hpp) diff --git a/include/stdx/ct_format.hpp b/include/stdx/ct_format.hpp index 24df3ce..7eb2da9 100644 --- a/include/stdx/ct_format.hpp +++ b/include/stdx/ct_format.hpp @@ -6,8 +6,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -107,8 +109,8 @@ template CONSTEVAL auto split_specifiers(std::string_view fmt) { } template -concept cx_value = requires { typename T::cx_value_t; } or - requires(T t) { ct_string_from_type(t); }; +concept fmt_cx_value = + is_cx_value_v or requires(T t) { ct_string_from_type(t); }; template CONSTEVAL auto arg_value(std::integral_constant) { @@ -125,7 +127,7 @@ template CONSTEVAL auto arg_value(type_identity) { template CONSTEVAL auto arg_value(cts_t) { return S; } -CONSTEVAL auto arg_value(cx_value auto a) { +CONSTEVAL auto arg_value(fmt_cx_value auto a) { if constexpr (requires { ct_string_from_type(a); }) { return ct_string_from_type(a); } else if constexpr (std::is_enum_v) { @@ -258,4 +260,11 @@ constexpr auto num_fmt_specifiers = } // namespace v1 } // namespace stdx +// NOLINTBEGIN(cppcoreguidelines-macro-usage) + +#define STDX_CT_FORMAT(S, ...) \ + stdx::ct_format(STDX_MAP(CX_WRAP __VA_OPT__(, ) __VA_ARGS__)) + +// NOLINTEND(cppcoreguidelines-macro-usage) + #endif diff --git a/include/stdx/ct_string.hpp b/include/stdx/ct_string.hpp index 4fd21b5..f3afcac 100644 --- a/include/stdx/ct_string.hpp +++ b/include/stdx/ct_string.hpp @@ -173,6 +173,8 @@ template struct ct_helper>; template CONSTEVAL auto ct() { return cts_t{}; } +template constexpr auto is_ct_v> = true; + inline namespace literals { inline namespace ct_string_literals { template CONSTEVAL_UDL auto operator""_cts() { return S; } diff --git a/include/stdx/pp_map.hpp b/include/stdx/pp_map.hpp new file mode 100644 index 0000000..3a06ab2 --- /dev/null +++ b/include/stdx/pp_map.hpp @@ -0,0 +1,39 @@ +#pragma once + +// NOLINTBEGIN(cppcoreguidelines-macro-usage) + +#define STDX_EVAL0(...) __VA_ARGS__ +#define STDX_EVAL1(...) STDX_EVAL0(STDX_EVAL0(STDX_EVAL0(__VA_ARGS__))) +#define STDX_EVAL2(...) STDX_EVAL1(STDX_EVAL1(STDX_EVAL1(__VA_ARGS__))) +#define STDX_EVAL3(...) STDX_EVAL2(STDX_EVAL2(STDX_EVAL2(__VA_ARGS__))) +#define STDX_EVAL4(...) STDX_EVAL3(STDX_EVAL3(STDX_EVAL3(__VA_ARGS__))) +#define STDX_EVAL5(...) STDX_EVAL4(STDX_EVAL4(STDX_EVAL4(__VA_ARGS__))) +#define STDX_EVAL(...) STDX_EVAL5(__VA_ARGS__) + +#define STDX_MAP_END(...) +#define STDX_MAP_OUT + +#define STDX_EMPTY() +#define STDX_DEFER(id) id STDX_EMPTY() + +#define STDX_MAP_GET_END2() 0, STDX_MAP_END +#define STDX_MAP_GET_END1(...) STDX_MAP_GET_END2 +#define STDX_MAP_GET_END(...) STDX_MAP_GET_END1 +#define STDX_MAP_NEXT0(test, next, ...) next STDX_MAP_OUT +#define STDX_MAP_NEXT1(test, next) STDX_DEFER(STDX_MAP_NEXT0)(test, next, 0) +#define STDX_MAP_NEXT(test, next) STDX_MAP_NEXT1(STDX_MAP_GET_END test, next) +#define STDX_MAP_INC(X) STDX_MAP_INC_##X + +#define STDX_MAP_A(f, x, peek, ...) \ + , f(x) STDX_DEFER(STDX_MAP_NEXT(peek, STDX_MAP_B))(f, peek, __VA_ARGS__) +#define STDX_MAP_B(f, x, peek, ...) \ + , f(x) STDX_DEFER(STDX_MAP_NEXT(peek, STDX_MAP_A))(f, peek, __VA_ARGS__) + +#define STDX_DROP0(X, ...) __VA_ARGS__ +#define STDX_DROP1(...) STDX_DROP0(__VA_ARGS__) + +#define STDX_MAP(f, ...) \ + __VA_OPT__(STDX_DROP1( \ + 0 STDX_EVAL(STDX_MAP_A(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)))) + +// NOLINTEND(cppcoreguidelines-macro-usage) diff --git a/include/stdx/static_assert.hpp b/include/stdx/static_assert.hpp index 53bb34e..abf8c64 100644 --- a/include/stdx/static_assert.hpp +++ b/include/stdx/static_assert.hpp @@ -26,7 +26,7 @@ template constexpr auto ct_check = ct_check_t{}; namespace detail { template constexpr auto static_format() { constexpr auto make_ct = []() { - if constexpr (cx_value) { + if constexpr (fmt_cx_value) { return V; } else { return CX_VALUE(V); diff --git a/include/stdx/utility.hpp b/include/stdx/utility.hpp index d27c507..9afcce9 100644 --- a/include/stdx/utility.hpp +++ b/include/stdx/utility.hpp @@ -148,14 +148,18 @@ struct from_any { struct type_val { template >> - friend constexpr auto operator+(T &&t, U &&) -> T { + friend constexpr auto operator+(T t, U const &) -> T { return t; } friend constexpr auto operator+(type_val const &f) -> type_val { return f; } // NOLINTNEXTLINE(google-explicit-constructor) template constexpr operator T() const { - extern auto cxv_type_val_get_t(T *) -> T; - return cxv_type_val_get_t(nullptr); + if constexpr (std::is_default_constructible_v) { + return T{}; + } else { + extern auto cxv_type_val_get_t(T *) -> T; + return cxv_type_val_get_t(nullptr); + } } }; @@ -202,6 +206,9 @@ template struct ct_helper { T value; }; template ct_helper(T) -> ct_helper; + +template CONSTEVAL auto cx_detect0() {} +CONSTEVAL auto cx_detect1(auto) { return 0; } } // namespace detail template CONSTEVAL auto ct() { @@ -209,6 +216,12 @@ template CONSTEVAL auto ct() { } template CONSTEVAL auto ct() { return type_identity{}; } +template constexpr auto is_ct_v = false; +template +constexpr auto is_ct_v> = true; +template constexpr auto is_ct_v> = true; +template constexpr auto is_ct_v = is_ct_v; + #endif } // namespace v1 } // namespace stdx @@ -221,28 +234,92 @@ template CONSTEVAL auto ct() { return type_identity{}; } #ifndef CX_VALUE #define CX_VALUE(...) \ - []() constexpr { \ + [&]() constexpr { \ STDX_PRAGMA(diagnostic push) \ STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \ STDX_PRAGMA(diagnostic ignored "-Wunused-value") \ - if constexpr (decltype(stdx::cxv_detail::is_type< \ - stdx::cxv_detail::from_any( \ + if constexpr (decltype(::stdx::cxv_detail::is_type< \ + ::stdx::cxv_detail::from_any( \ __VA_ARGS__)>())::value) { \ - return stdx::overload{stdx::cxv_detail::cx_base{}, [] { \ - return stdx::type_identity< \ - decltype(stdx::cxv_detail::type_of< \ - stdx::cxv_detail::from_any( \ - __VA_ARGS__)>())>{}; \ - }}; \ + return ::stdx::overload{ \ + ::stdx::cxv_detail::cx_base{}, [&] { \ + return ::stdx::type_identity< \ + decltype(::stdx::cxv_detail::type_of< \ + ::stdx::cxv_detail::from_any( \ + __VA_ARGS__)>())>{}; \ + }}; \ } else { \ - return stdx::overload{stdx::cxv_detail::cx_base{}, [] { \ - return (__VA_ARGS__) + \ - stdx::cxv_detail::type_val{}; \ - }}; \ + return ::stdx::overload{::stdx::cxv_detail::cx_base{}, [&] { \ + return (__VA_ARGS__) + \ + ::stdx::cxv_detail::type_val{}; \ + }}; \ } \ STDX_PRAGMA(diagnostic pop) \ }() #endif +#if __cplusplus >= 202002L + +#define CT_WRAP(X) \ + [&](auto f) { \ + if constexpr (::stdx::is_ct_v) { \ + return f(); \ + } else if constexpr (requires { \ + ::stdx::ct<[&]() constexpr { return X; }()>; \ + }) { \ + return ::stdx::ct<[&]() constexpr { return X; }()>(); \ + } else { \ + return f(); \ + } \ + }([&] { return X; }) + +#ifdef __clang__ +#define CX_DETECT(X) std::is_empty_v +#else +namespace stdx { +inline namespace v1 { +template constexpr auto cx_detect0() {} +constexpr auto cx_detect1(auto) { return 0; } +} // namespace v1 +} // namespace stdx +#define CX_DETECT(X) \ + requires { \ + ::stdx::cx_detect0<::stdx::cx_detect1( \ + (X) + ::stdx::cxv_detail::type_val{})>; \ + } +#endif + +#define CX_WRAP(X) \ + [&](F f) { \ + STDX_PRAGMA(diagnostic push) \ + STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \ + if constexpr (::stdx::is_cx_value_v>) { \ + return f(); \ + } else if constexpr (CX_DETECT(X)) { \ + if constexpr (decltype(::stdx::cxv_detail::is_type< \ + ::stdx::cxv_detail::from_any( \ + X)>())::value) { \ + return ::stdx::overload{ \ + ::stdx::cxv_detail::cx_base{}, [&] { \ + return ::stdx::type_identity< \ + decltype(::stdx::cxv_detail::type_of< \ + ::stdx::cxv_detail::from_any(X)>())>{}; \ + }}; \ + } else { \ + return ::stdx::overload{::stdx::cxv_detail::cx_base{}, f}; \ + } \ + } else { \ + return f(); \ + } \ + STDX_PRAGMA(diagnostic pop) \ + }([&] { \ + STDX_PRAGMA(diagnostic push) \ + STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \ + return (X) + ::stdx::cxv_detail::type_val{}; \ + STDX_PRAGMA(diagnostic pop) \ + }) + +#endif + // NOLINTEND(cppcoreguidelines-macro-usage) // NOLINTEND(modernize-use-constraints) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e4fd72c..07ce849 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,6 +54,7 @@ add_tests( optional overload panic + pp_map priority ranges remove_cvref diff --git a/test/ct_format.cpp b/test/ct_format.cpp index ed628af..f00485a 100644 --- a/test/ct_format.cpp +++ b/test/ct_format.cpp @@ -242,6 +242,52 @@ constexpr auto ct_format_as(S const &s) { } // namespace user TEST_CASE("user-defined formatting", "[ct_format]") { - auto r = stdx::ct_format<"Hello {}">(user::S{42}); - CHECK(r == stdx::format_result{"Hello S: {}"_ctst, stdx::make_tuple(42)}); + auto r = stdx::ct_format<"Hello {}">(user::S{17}); + CHECK(r == stdx::format_result{"Hello S: {}"_ctst, stdx::make_tuple(17)}); +} + +TEST_CASE("FORMAT with no arguments", "[ct_format]") { + STATIC_REQUIRE(STDX_CT_FORMAT("Hello") == "Hello"_fmt_res); +} + +TEST_CASE("FORMAT a compile-time string argument", "[ct_format]") { + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", "world") == + "Hello world"_fmt_res); +} + +TEST_CASE("FORMAT a compile-time int argument", "[ct_format]") { + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", 17) == "Hello 17"_fmt_res); +} + +TEST_CASE("FORMAT a type argument", "[ct_format]") { + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", int) == "Hello int"_fmt_res); +} + +TEST_CASE("FORMAT a constexpr string argument", "[ct_format]") { + constexpr static auto S = "world"_cts; + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); +} + +TEST_CASE("FORMAT a constexpr int argument", "[ct_format]") { + constexpr static auto I = 17; + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res); +} + +#ifdef __clang__ +TEST_CASE("FORMAT a constexpr nonstatic string_view argument", "[ct_format]") { + constexpr auto S = std::string_view{"world"}; + constexpr auto expected = + stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(S)}; + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == expected); +} +#endif + +TEST_CASE("FORMAT a constexpr string_view argument", "[ct_format]") { + constexpr static auto S = std::string_view{"world"}; + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); +} + +TEST_CASE("FORMAT an integral_constant argument", "[ct_format]") { + constexpr static auto I = std::integral_constant{}; + STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res); } diff --git a/test/ct_string.cpp b/test/ct_string.cpp index 756400b..f9b16a0 100644 --- a/test/ct_string.cpp +++ b/test/ct_string.cpp @@ -165,3 +165,30 @@ TEST_CASE("operator+ works to concat cts_t and ct_string", "[ct_string]") { STATIC_REQUIRE("Hello"_ctst + " world"_cts == "Hello world"_cts); STATIC_REQUIRE("Hello"_cts + " world"_ctst == "Hello world"_cts); } + +TEST_CASE("is_ct (ct_string)", "[ct_string]") { + using namespace stdx::ct_string_literals; + constexpr auto v1 = stdx::ct<"Hello">(); + STATIC_REQUIRE(stdx::is_ct_v); +} + +TEST_CASE("CT_WRAP", "[ct_string]") { + using namespace stdx::ct_string_literals; + auto x1 = "hello"_cts; + STATIC_REQUIRE(std::is_same_v>); + CHECK(CT_WRAP(x1) == "hello"_cts); + + auto x2 = "hello"_ctst; + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(x2) == "hello"_ctst); + + constexpr static auto x3 = "hello"_cts; + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(x3) == "hello"_ctst); + + []() { + STATIC_REQUIRE( + std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(X) == "hello"_ctst); + }.template operator()<"hello">(); +} diff --git a/test/pp_map.cpp b/test/pp_map.cpp new file mode 100644 index 0000000..dbded8f --- /dev/null +++ b/test/pp_map.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include + +namespace { +auto count_args = [](auto const &...args) { return sizeof...(args); }; +} // namespace + +TEST_CASE("pp_map zero arguments", "[pp_map]") { +#ifdef __clang__ + STDX_PRAGMA(diagnostic push) + STDX_PRAGMA(diagnostic ignored "-Wgnu-zero-variadic-macro-arguments") +#endif + static_assert(count_args(STDX_MAP(int)) == 0); +#ifdef __clang__ + STDX_PRAGMA(diagnostic pop) +#endif +} + +TEST_CASE("pp_map one argument", "[pp_map]") { + static_assert(count_args(STDX_MAP(double, 1)) == 1.0); +} + +TEST_CASE("pp_map n arguments", "[pp_map]") { + static_assert(count_args(STDX_MAP(double, 1, 2, 3)) == 3); +} + +TEST_CASE("pp_map parenthesized arguments", "[pp_map]") { + static_assert(count_args(STDX_MAP(double, ((void)1, 2), 3)) == 2); +} diff --git a/test/utility.cpp b/test/utility.cpp index ef826e5..8d924c2 100644 --- a/test/utility.cpp +++ b/test/utility.cpp @@ -271,4 +271,97 @@ TEST_CASE("ct (type)", "[utility]") { STATIC_REQUIRE(std::is_same_v const>); } +TEST_CASE("is_ct", "[utility]") { + constexpr auto x1 = stdx::ct<42>(); + STATIC_REQUIRE(stdx::is_ct_v); + constexpr auto x2 = stdx::ct(); + STATIC_REQUIRE(stdx::is_ct_v); +} + +TEST_CASE("CT_WRAP", "[utility]") { + auto x1 = 17; + STATIC_REQUIRE(std::is_same_v); + CHECK(CT_WRAP(x1) == 17); + + auto x2 = stdx::ct<17>(); + STATIC_REQUIRE( + std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(x2).value == 17); + + auto const x3 = 17; + STATIC_REQUIRE( + std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(x3).value == 17); + + constexpr static auto x4 = 17; + STATIC_REQUIRE( + std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(x4).value == 17); + + []() { + STATIC_REQUIRE(std::is_same_v>); + STATIC_REQUIRE(CT_WRAP(X).value == 17); + }.template operator()<17>(); +} + +TEST_CASE("CX_WRAP integer runtime arg", "[utility]") { + auto x = 17; + STATIC_REQUIRE(std::is_same_v); + CHECK(CX_WRAP(x) == 17); +} + +TEST_CASE("CX_WRAP string_view runtime arg", "[utility]") { + auto x = std::string_view{"hello"}; + STATIC_REQUIRE(std::is_same_v); + CHECK(CX_WRAP(x) == std::string_view{"hello"}); +} + +TEST_CASE("CX_WRAP const integral type", "[utility]") { + auto const x = 17; + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(x)() == 17); +} + +TEST_CASE("CX_WRAP constexpr integral type", "[utility]") { + constexpr auto x = 17; + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(x)() == 17); +} + +TEST_CASE("CX_WRAP constexpr non-structural type", "[utility]") { + constexpr static auto x = std::string_view{"hello"}; + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(x)() == std::string_view{"hello"}); +} + +TEST_CASE("CX_WRAP integer literal", "[utility]") { + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(17)() == 17); +} + +TEST_CASE("CX_WRAP string literal", "[utility]") { + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP("hello")() == std::string_view{"hello"}); +} + +TEST_CASE("CX_WRAP existing CX_VALUE", "[utility]") { + auto x = CX_VALUE(17); + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(x)() == 17); +} + +TEST_CASE("CX_WRAP template argument", "[utility]") { + [] { + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE(CX_WRAP(x)() == 17); + }.template operator()<17>(); +} + +TEST_CASE("CX_WRAP type argument", "[utility]") { + STATIC_REQUIRE(stdx::is_cx_value_v); + STATIC_REQUIRE( + std::is_same_v>); +} + #endif