diff --git a/include/stdx/ct_format.hpp b/include/stdx/ct_format.hpp index 2a32fdb..a7fc15b 100644 --- a/include/stdx/ct_format.hpp +++ b/include/stdx/ct_format.hpp @@ -13,8 +13,11 @@ #include #include +#include + #include #include +#include #include #include #include @@ -25,12 +28,45 @@ template constexpr auto format_as(stdx::ct_string const &s) { return std::string_view{s}; } -template struct format_result { +template struct format_span { + constexpr static auto begin = Begin; + constexpr static auto end = End; + + private: + friend constexpr auto operator==(format_span const &, format_span const &) + -> bool = default; +}; + +namespace detail { +template struct apply_span_offset_q { + template + using fn = format_span; +}; + +template +using apply_offset = + boost::mp11::mp_transform_q, L>; +} // namespace detail + +template struct ct_format_arg { + using type_t = T; + friend constexpr auto operator==(ct_format_arg const &, + ct_format_arg const &) -> bool = default; +}; + +template +using is_compile_time_arg = + std::bool_constant()>; + +template struct format_result { + static_assert(Args::size() == boost::mp11::mp_size::value); + CONSTEVAL static auto ct_string_convertible() -> std::bool_constant; [[no_unique_address]] Str str; [[no_unique_address]] Args args{}; + using spans_t = Spans; friend constexpr auto operator+(format_result const &fr) requires(decltype(ct_string_convertible())::value) @@ -49,13 +85,17 @@ template struct format_result { format_result const &) -> bool = default; }; -template - requires(Args::size() == 0 and is_cx_value_v) -struct format_result { +template + requires(boost::mp11::mp_all_of::value and + is_cx_value_v) +struct format_result { + static_assert(Args::size() == boost::mp11::mp_size::value); + CONSTEVAL static auto ct_string_convertible() -> std::true_type; [[no_unique_address]] Str str; [[no_unique_address]] Args args{}; + using spans_t = Spans; friend constexpr auto operator+(format_result const &fr) { return +fr.str; } @@ -67,14 +107,15 @@ struct format_result { format_result const &) -> bool = default; }; -template -format_result(Str, Args) -> format_result; -template format_result(Str) -> format_result>; +template , typename Str, typename Args = tuple<>> +constexpr auto make_format_result(Str s, Args args = {}) { + return format_result{s, std::move(args)}; +} inline namespace literals { inline namespace ct_string_literals { template CONSTEVAL_UDL auto operator""_fmt_res() { - return format_result{cts_t{}}; + return make_format_result(cts_t{}); } } // namespace ct_string_literals } // namespace literals @@ -104,10 +145,19 @@ CONSTEVAL auto count_specifiers(std::string_view fmt) -> std::size_t { return count; } +struct split_spec { + std::string_view view; + std::size_t start{}; + + private: + friend constexpr auto operator==(split_spec const &, split_spec const &) + -> bool = default; +}; + template CONSTEVAL auto split_specifiers(std::string_view fmt) - -> std::array { - auto splits = std::array{}; + -> std::array { + auto splits = std::array{}; auto count = std::size_t{}; auto split_start = fmt.begin(); @@ -117,11 +167,15 @@ CONSTEVAL auto split_specifiers(std::string_view fmt) if (split_end != fmt.end()) { ++split_end; } - splits[count++] = std::string_view{split_start, split_end}; + splits[count++] = { + std::string_view{split_start, split_end}, + static_cast(std::distance(split_start, spec_start))}; split_start = split_end; spec_start = find_spec(split_start, fmt.end()); } - splits[count++] = std::string_view{split_start, spec_start}; + splits[count++] = { + std::string_view{split_start, spec_start}, + static_cast(std::distance(split_start, spec_start))}; return splits; } @@ -146,30 +200,50 @@ template CONSTEVAL auto arg_value(type_identity) { template CONSTEVAL auto arg_value(cts_t) { return S; } CONSTEVAL auto arg_value(fmt_cx_value auto a) { - if constexpr (requires { ct_string_from_type(a); }) { + if constexpr (is_specialization_of_v) { + return a; + } else if constexpr (requires { arg_value(a()); }) { + return arg_value(a()); + } else if constexpr (requires { ct_string_from_type(a); }) { return ct_string_from_type(a); } else if constexpr (std::is_enum_v) { return enum_as_string(); - } else if constexpr (requires { arg_value(a()); }) { - return arg_value(a()); } else { return a(); } } -template -constexpr auto operator+(format_result r, S s) { - return format_result{r.str + s, r.args}; +template CONSTEVAL auto arg_type(T) -> T; + +template +CONSTEVAL auto arg_type(std::integral_constant) -> T; + +CONSTEVAL auto arg_type(fmt_cx_value auto a) { + if constexpr (requires { ct_string_from_type(a); }) { + return ct_string_from_type(a); + } else { + return a(); + } +} + +template +constexpr auto operator+(format_result r, S s) { + return make_format_result(r.str + s, std::move(r.args)); } -template -constexpr auto operator+(S s, format_result r) { - return format_result{s + r.str, r.args}; +template +constexpr auto operator+(S s, format_result r) { + return make_format_result>( + s + r.str, std::move(r.args)); } -template -constexpr auto operator+(format_result r1, format_result r2) { - return format_result{r1.str + r2.str, tuple_cat(r1.args, r2.args)}; +template +constexpr auto operator+(format_result r1, + format_result r2) { + return make_format_result>>( + r1.str + r2.str, tuple_cat(std::move(r1.args), std::move(r2.args))); } template struct null_output; @@ -197,13 +271,14 @@ CONSTEVAL auto convert_output() { } template -CONSTEVAL auto perform_format(auto s, auto v) -> ct_string { +CONSTEVAL auto perform_format(auto s, auto const &v) -> ct_string { ct_string cts{}; fmt::format_to(cts.begin(), s, v); return cts; } -template constexpr auto format1(Arg arg) { +template +constexpr auto format1(Arg arg) { if constexpr (requires { arg_value(arg); }) { constexpr auto fmtstr = STDX_FMT_COMPILE(Fmt); constexpr auto a = arg_value(arg); @@ -212,17 +287,25 @@ template constexpr auto format1(Arg arg) { constexpr auto s = convert_input(a.str); constexpr auto sz = fmt::formatted_size(fmtstr, s); constexpr auto cts = perform_format(fmtstr, s); - return format_result{cts_t{}, a.args}; + using Spans = typename std::remove_cvref_t::spans_t; + return make_format_result>( + cts_t{}, a.args); } else { + using arg_t = stdx::remove_cvref_t; constexpr auto sz = fmt::formatted_size(fmtstr, a); constexpr auto cts = perform_format(fmtstr, a); - return format_result{cts_t{}}; + using Spans = type_list>; + return make_format_result(cts_t{}, + tuple{ct_format_arg{}}); } } else if constexpr (is_specialization_of_v) { - auto const sub_result = format1(arg.str); - return format_result{sub_result.str, arg.args}; + auto const sub_result = format1(arg.str); + using Spans = typename Arg::spans_t; + return make_format_result>( + sub_result.str, std::move(arg).args); } else { - return format_result{cts_t{}, tuple{arg}}; + using Spans = type_list>; + return make_format_result(cts_t{}, tuple{std::move(arg)}); } } @@ -237,13 +320,24 @@ template struct fmt_data { constexpr static auto fmt = std::string_view{Fmt}; constexpr static auto N = count_specifiers(fmt); constexpr static auto splits = split_specifiers(fmt); - constexpr static auto last_cts = to_ct_string(splits[N]); + constexpr static auto last_cts = + to_ct_string(splits[N].view); }; -template -constexpr auto ct_format_as(T const &t) -> decltype(auto) { - return (t); -} +[[maybe_unused]] constexpr inline struct format_as_t { + template + requires true + constexpr auto operator()(T &&t) const + noexcept(noexcept(ct_format_as(std::forward(t)))) + -> decltype(ct_format_as(std::forward(t))) { + return ct_format_as(std::forward(t)); + } + + template + constexpr auto operator()(T &&t) const -> decltype(auto) { + return T(std::forward(t)); + } +} format_as; } // namespace detail template (auto &&arg) { - constexpr auto cts = - detail::to_ct_string(data::splits[I]); - return detail::format1(FWD(arg)); + [[maybe_unused]] auto const format1 = [](auto &&arg) { + constexpr auto cts = detail::to_ct_string( + data::splits[I].view); + return detail::format1(FWD(arg)); }; - auto const result = [&](std::index_sequence) { - using detail::ct_format_as; - return (format1.template operator()(ct_format_as(FWD(args))) + ... + - format_result{cts_t{}}); - }(std::make_index_sequence{}); + auto result = [&](std::index_sequence, + auto &&...as) { + return (format1.template operator()(detail::format_as(FWD(as))) + + ... + make_format_result(cts_t{})); + }(std::make_index_sequence{}, FWD(args)...); constexpr auto str = detail::convert_output(); - return format_result{str, result.args}; + using Spans = typename std::remove_cvref_t::spans_t; + return make_format_result(str, std::move(result).args); }; template diff --git a/include/stdx/ct_string.hpp b/include/stdx/ct_string.hpp index fe7cb4d..a857089 100644 --- a/include/stdx/ct_string.hpp +++ b/include/stdx/ct_string.hpp @@ -147,6 +147,7 @@ template struct cts_t { friend constexpr auto operator+(cts_t const &) { return value; } constexpr auto operator()() const noexcept { return value; } using cx_value_t [[maybe_unused]] = void; + constexpr static auto size = S.size; }; template diff --git a/test/ct_format.cpp b/test/ct_format.cpp index 2808225..fdd729b 100644 --- a/test/ct_format.cpp +++ b/test/ct_format.cpp @@ -1,3 +1,5 @@ +#include "detail/tuple_types.hpp" + #include #include @@ -12,104 +14,175 @@ using namespace stdx::ct_string_literals; TEST_CASE("detect string format specifiers", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(stdx::detail::count_specifiers("{}"sv) == 1u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{} {}"sv) == 2u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{"sv) == 0u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{{"sv) == 0u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{{{}"sv) == 1u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{{}}"sv) == 0u); - STATIC_REQUIRE(stdx::detail::count_specifiers("{{{{"sv) == 0u); + STATIC_CHECK(stdx::detail::count_specifiers("{}"sv) == 1u); + STATIC_CHECK(stdx::detail::count_specifiers("{} {}"sv) == 2u); + STATIC_CHECK(stdx::detail::count_specifiers("{"sv) == 0u); + STATIC_CHECK(stdx::detail::count_specifiers("{{"sv) == 0u); + STATIC_CHECK(stdx::detail::count_specifiers("{{{}"sv) == 1u); + STATIC_CHECK(stdx::detail::count_specifiers("{{}}"sv) == 0u); + STATIC_CHECK(stdx::detail::count_specifiers("{{{{"sv) == 0u); } TEST_CASE("split format string by specifiers", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(stdx::detail::split_specifiers<1>("hello"sv) == - std::array{"hello"sv}); - STATIC_REQUIRE(stdx::detail::split_specifiers<2>("{}"sv) == - std::array{"{}"sv, ""sv}); - STATIC_REQUIRE(stdx::detail::split_specifiers<3>("{} {}"sv) == - std::array{"{}"sv, " {}"sv, ""sv}); - STATIC_REQUIRE(stdx::detail::split_specifiers<2>("{{{}"sv) == - std::array{"{{{}"sv, ""sv}); - STATIC_REQUIRE(stdx::detail::split_specifiers<2>("{} hello"sv) == - std::array{"{}"sv, " hello"sv}); + using SS = stdx::detail::split_spec; + STATIC_CHECK(stdx::detail::split_specifiers<1>("hello"sv) == + std::array{SS{"hello"sv, 5}}); + STATIC_CHECK(stdx::detail::split_specifiers<2>("{}"sv) == + std::array{SS{"{}"sv, 0}, SS{""sv, 0}}); + STATIC_CHECK(stdx::detail::split_specifiers<3>("{} {}"sv) == + std::array{SS{"{}"sv, 0}, SS{" {}"sv, 1}, SS{""sv, 0}}); + STATIC_CHECK(stdx::detail::split_specifiers<2>("{{{}"sv) == + std::array{SS{"{{{}"sv, 2}, SS{""sv, 0}}); + STATIC_CHECK(stdx::detail::split_specifiers<2>("{} hello"sv) == + std::array{SS{"{}"sv, 0}, SS{" hello"sv, 6}}); } TEST_CASE("format a static string", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello">() == "Hello"_fmt_res); + STATIC_CHECK(stdx::ct_format<"Hello">() == "Hello"_fmt_res); } TEST_CASE("format a compile-time stringish argument (CX_VALUE)", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE("world"sv)) == - "Hello world"_fmt_res); - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE("world"_cts)) == - "Hello world"_fmt_res); - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE("world")) == - "Hello world"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE("world"sv)) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg{}})); + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE("world"_cts)) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE("world")) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format a compile-time stringish argument (ct)", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(stdx::ct_format<"Hello {}">("world"_ctst) == - "Hello world"_fmt_res); - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct<"world">()) == - "Hello world"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">("world"_ctst) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); + STATIC_CHECK(stdx::ct_format<"Hello {}">(stdx::ct<"world">()) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("format a compile-time integral argument (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(42)) == - "Hello 42"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE(42)) == + stdx::make_format_result( + "Hello 42"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format a negative compile-time integral argument (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(-42)) == - "Hello -42"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}">(CX_VALUE(-42)) == + stdx::make_format_result( + "Hello -42"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format most negative compile-time integral argument (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">( - CX_VALUE(std::numeric_limits::min())) == - "Hello -2147483648"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}">( + CX_VALUE(std::numeric_limits::min())) == + stdx::make_format_result( + "Hello -2147483648"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format zero (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(0)) == - "Hello 0"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE(0)) == + stdx::make_format_result( + "Hello 0"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format a char (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}orld">(CX_VALUE('w')) == - "Hello world"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}orld">(CX_VALUE('w')) == + stdx::make_format_result( + "Hello world"_ctst, stdx::tuple{stdx::ct_format_arg{}})); +} + +#ifndef STDX_FREESTANDING +namespace move_test { +struct move_only { + constexpr move_only() = default; + constexpr move_only(int x) : value{x} {} + constexpr move_only(move_only &&) = default; + constexpr auto operator=(move_only &&) noexcept -> move_only & = default; + + friend constexpr auto operator==(move_only const &, move_only const &) + -> bool = default; + + int value; +}; + +[[nodiscard]] constexpr auto format_as(move_only const &) -> std::string_view { + return "17"; +} +} // namespace move_test + +TEST_CASE("format a move-only argument (CX_VALUE)", "[ct_format]") { + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}">(CX_VALUE(move_test::move_only{17})) == + stdx::make_format_result( + "Hello 17"_ctst, + stdx::tuple{stdx::ct_format_arg{}})); } +#endif TEST_CASE("format a compile-time integral argument (ct)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct<42>()) == - "Hello 42"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(stdx::ct<42>()) == + stdx::make_format_result( + "Hello 42"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format a type argument (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(int)) == - "Hello int"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}">(CX_VALUE(int)) == + stdx::make_format_result( + "Hello int"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("format a type argument (ct)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct()) == - "Hello int"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {}">(stdx::ct()) == + stdx::make_format_result( + "Hello int"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("format a compile-time argument with different base", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello 0x{:x}">(CX_VALUE(42)) == - "Hello 0x2a"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello 0x{:x}">(CX_VALUE(42)) == + stdx::make_format_result( + "Hello 0x2a"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } #ifndef STDX_FREESTANDING TEST_CASE("format a compile-time argument with fmt spec", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {:*>#6x}">(CX_VALUE(42)) == - "Hello **0x2a"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + stdx::ct_format<"Hello {:*>#6x}">(CX_VALUE(42)) == + stdx::make_format_result( + "Hello **0x2a"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } #endif @@ -118,64 +191,105 @@ enum struct E { A }; } TEST_CASE("format a compile-time enum argument (CX_VALUE)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) == - "Hello A"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) == + stdx::make_format_result( + "Hello A"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("format a compile-time enum argument (ct)", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct()) == - "Hello A"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(stdx::ct_format<"Hello {}">(stdx::ct()) == + stdx::make_format_result( + "Hello A"_ctst, stdx::tuple{stdx::ct_format_arg{}})); +} + +TEST_CASE("format multiple compile-time arguments", "[ct_format]") { + using expected_spans_t = + stdx::type_list, stdx::format_span<8, 13>>; + STATIC_CHECK( + stdx::ct_format<"Hello {} {}">(CX_VALUE(E::A), CX_VALUE("world")) == + stdx::make_format_result( + "Hello A world"_ctst, + stdx::tuple{stdx::ct_format_arg{}, + stdx::ct_format_arg{}})); } TEST_CASE("format a runtime argument", "[ct_format]") { constexpr auto x = 17; - constexpr auto expected = - stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(x)}; - + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello {}"_ctst, stdx::tuple{x}); CHECK(stdx::ct_format<"Hello {}">(x) == expected); - STATIC_REQUIRE(stdx::ct_format<"Hello {}">(x) == expected); + STATIC_CHECK(stdx::ct_format<"Hello {}">(x) == expected); +} + +TEST_CASE("format a move-only runtime argument", "[ct_format]") { + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello {}"_ctst, stdx::tuple{move_only{17}}); + CHECK(stdx::ct_format<"Hello {}">(move_only{17}) == expected); + STATIC_CHECK(stdx::ct_format<"Hello {}">(move_only{17}) == expected); } TEST_CASE("format a compile-time and a runtime argument (1)", "[ct_format]") { constexpr auto x = 17; - constexpr auto expected = - stdx::format_result{"Hello int {}"_ctst, stdx::make_tuple(x)}; - + using expected_spans_t = + stdx::type_list, stdx::format_span<10, 12>>; + constexpr auto expected = stdx::make_format_result( + "Hello int {}"_ctst, + stdx::tuple{stdx::ct_format_arg>{}, x}); CHECK(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected); - STATIC_REQUIRE(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == - expected); + STATIC_CHECK(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected); } TEST_CASE("format a compile-time and a runtime argument (2)", "[ct_format]") { - STATIC_REQUIRE( - stdx::ct_format<"Hello {} {}">(42, CX_VALUE(int)) == - stdx::format_result{"Hello {} int"_ctst, stdx::make_tuple(42)}); + using expected_spans_t = + stdx::type_list, stdx::format_span<9, 12>>; + constexpr auto expected = stdx::make_format_result( + "Hello {} int"_ctst, + stdx::tuple{42, stdx::ct_format_arg>{}}); + STATIC_CHECK(stdx::ct_format<"Hello {} {}">(42, CX_VALUE(int)) == expected); } TEST_CASE("format multiple runtime arguments", "[ct_format]") { - STATIC_REQUIRE( - stdx::ct_format<"Hello {} {}">(42, 17) == - stdx::format_result{"Hello {} {}"_ctst, stdx::make_tuple(42, 17)}); + using expected_spans_t = + stdx::type_list, stdx::format_span<9, 11>>; + constexpr auto expected = stdx::make_format_result( + "Hello {} {}"_ctst, stdx::tuple{42, 17}); + STATIC_CHECK(stdx::ct_format<"Hello {} {}">(42, 17) == expected); } TEST_CASE("format multiple mixed arguments", "[ct_format]") { using namespace std::string_view_literals; auto b = "B"sv; - CHECK(stdx::ct_format<"Hello {} {} {} {} world">(42, CX_VALUE("A"sv), b, - CX_VALUE(int)) == - stdx::format_result{"Hello {} A {} int world"_ctst, - stdx::make_tuple(42, "B"sv)}); - STATIC_REQUIRE(stdx::ct_format<"Hello {} {} {} {} world">( - 42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) == - stdx::format_result{"Hello {} A {} int world"_ctst, - stdx::make_tuple(42, "B"sv)}); + + using expected_spans_t = + stdx::type_list, stdx::format_span<9, 10>, + stdx::format_span<11, 13>, stdx::format_span<14, 17>>; + constexpr auto expected = stdx::make_format_result( + "Hello {} A {} int world"_ctst, + stdx::tuple{42, stdx::ct_format_arg{}, "B"sv, + stdx::ct_format_arg>{}}); + + CHECK(stdx::ct_format<"Hello {} {} {} {} world">( + 42, CX_VALUE("A"sv), b, CX_VALUE(int)) == expected); + STATIC_CHECK(stdx::ct_format<"Hello {} {} {} {} world">( + 42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) == expected); } TEST_CASE("format a format result", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"The value is {}.">( - stdx::ct_format<"(year={})">(2022)) == - stdx::format_result{"The value is (year={})."_ctst, - stdx::make_tuple(2022)}); + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "The value is (year={})."_ctst, stdx::tuple{2022}); + + STATIC_CHECK(stdx::ct_format<"The value is {}.">( + stdx::ct_format<"(year={})">(2022)) == expected); +} + +TEST_CASE("format an empty format result", "[ct_format]") { + STATIC_CHECK(stdx::ct_format<"The value is {}.">("2022"_fmt_res) == + "The value is 2022."_fmt_res); } namespace { @@ -185,20 +299,20 @@ template constexpr auto conversion_success = true; TEST_CASE("empty format_result can implicitly convert to ct_string", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE( + STATIC_CHECK( stdx::detail::format_convertible())>); - STATIC_REQUIRE(stdx::detail::format_convertible< - decltype(stdx::ct_format<"Hello {}">("world"_ctst))>); - STATIC_REQUIRE(not stdx::detail::format_convertible< - decltype(stdx::ct_format<"Hello {}">(42))>); + STATIC_CHECK(stdx::detail::format_convertible< + decltype(stdx::ct_format<"Hello {}">("world"_ctst))>); + STATIC_CHECK(not stdx::detail::format_convertible< + decltype(stdx::ct_format<"Hello {}">(42))>); - STATIC_REQUIRE(conversion_success()>); + STATIC_CHECK(conversion_success()>); } TEST_CASE("empty format_result can explicitly convert to ct_string", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(+stdx::ct_format<"Hello">() == "Hello"_cts); + STATIC_CHECK(+stdx::ct_format<"Hello">() == "Hello"_cts); } namespace { @@ -216,49 +330,68 @@ template } } // namespace -TEST_CASE("format_to a different type", "[ct_format]") { +TEST_CASE("format_to a different type (compile-time value)", "[ct_format]") { using namespace std::string_view_literals; - STATIC_REQUIRE(stdx::ct_format<"{}", string_constant>(CX_VALUE("A"sv)) == - stdx::format_result{string_constant{}}); - auto x = 17; - CHECK(stdx::ct_format<"{}", string_constant>(x) == - stdx::format_result{string_constant{}, - stdx::make_tuple(17)}); - STATIC_REQUIRE(stdx::ct_format<"{}", string_constant>(17) == - stdx::format_result{string_constant{}, - stdx::make_tuple(17)}); + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + string_constant{}, + stdx::tuple{stdx::ct_format_arg{}}); + + STATIC_CHECK(stdx::ct_format<"{}", string_constant>(CX_VALUE("A"sv)) == + expected); +} + +TEST_CASE("format_to a different type (runtime value)", "[ct_format]") { + using namespace std::string_view_literals; + + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + string_constant{}, stdx::tuple{17}); + + CHECK(stdx::ct_format<"{}", string_constant>(17) == expected); + STATIC_CHECK(stdx::ct_format<"{}", string_constant>(17) == expected); } TEST_CASE("format a string-type argument", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"Hello {}!">(string_constant{}) == - "Hello A!"_fmt_res); + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello A!"_ctst, + stdx::tuple{stdx::ct_format_arg>{}}); + + STATIC_CHECK(stdx::ct_format<"Hello {}!">(string_constant{}) == + expected); } TEST_CASE("format a format result with different type", "[ct_format]") { - STATIC_REQUIRE(stdx::ct_format<"A{}D", string_constant>( - stdx::ct_format<"B{}C", string_constant>(2022)) == - stdx::format_result{ - string_constant{}, - stdx::make_tuple(2022)}); + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + string_constant{}, + stdx::tuple{2022}); + + STATIC_CHECK(stdx::ct_format<"A{}D", string_constant>( + stdx::ct_format<"B{}C", string_constant>(2022)) == + expected); } TEST_CASE("format multiple mixed arguments with different type", "[ct_format]") { using namespace std::string_view_literals; auto b = "B"sv; + + using expected_spans_t = + stdx::type_list, stdx::format_span<9, 10>, + stdx::format_span<11, 13>, stdx::format_span<14, 17>>; + constexpr auto expected = stdx::make_format_result( + stdx::ct_string_to_type<"Hello {} A {} int world"_cts, + string_constant>(), + stdx::tuple{42, stdx::ct_format_arg{}, "B"sv, + stdx::ct_format_arg>{}}); + CHECK(stdx::ct_format<"Hello {} {} {} {} world", string_constant>( - 42, CX_VALUE("A"sv), b, CX_VALUE(int)) == - stdx::format_result{ - stdx::ct_string_to_type<"Hello {} A {} int world"_cts, - string_constant>(), - stdx::make_tuple(42, "B"sv)}); - STATIC_REQUIRE(stdx::ct_format<"Hello {} {} {} {} world", string_constant>( - 42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) == - stdx::format_result{ - stdx::ct_string_to_type<"Hello {} A {} int world"_cts, - string_constant>(), - stdx::make_tuple(42, "B"sv)}); + 42, CX_VALUE("A"sv), b, CX_VALUE(int)) == expected); + STATIC_CHECK(stdx::ct_format<"Hello {} {} {} {} world", string_constant>( + 42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) == expected); } TEST_CASE("num fmt specifiers", "[ct_format]") { @@ -277,64 +410,113 @@ constexpr auto ct_format_as(S const &s) { } // namespace user TEST_CASE("user-defined formatting", "[ct_format]") { + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello S: {}"_ctst, stdx::tuple{17}); + auto r = stdx::ct_format<"Hello {}">(user::S{17}); - CHECK(r == stdx::format_result{"Hello S: {}"_ctst, stdx::make_tuple(17)}); + CHECK(r == expected); } TEST_CASE("FORMAT with no arguments", "[ct_format]") { - STATIC_REQUIRE(STDX_CT_FORMAT("Hello") == "Hello"_fmt_res); + STATIC_CHECK(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); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", "world") == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("FORMAT a compile-time int argument", "[ct_format]") { - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", 17) == "Hello 17"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", 17) == + stdx::make_format_result( + "Hello 17"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } TEST_CASE("FORMAT a type argument", "[ct_format]") { - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", int) == "Hello int"_fmt_res); + using expected_spans_t = stdx::type_list>; + STATIC_CHECK( + STDX_CT_FORMAT("Hello {}", int) == + stdx::make_format_result( + "Hello int"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("FORMAT a constexpr ct_string argument", "[ct_format]") { + using expected_spans_t = stdx::type_list>; constexpr static auto S = "world"_cts; - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", S) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("FORMAT a cts_t argument", "[ct_format]") { + using expected_spans_t = stdx::type_list>; auto S = "world"_ctst; - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", S) == + stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg>{}})); } TEST_CASE("FORMAT a format_result argument", "[ct_format]") { + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "The value is (year={})."_ctst, stdx::tuple{2022}); + + auto S = stdx::ct_format<"(year={})">(2022); + CHECK(STDX_CT_FORMAT("The value is {}.", S) == expected); +} + +TEST_CASE("FORMAT an empty format_result argument", "[ct_format]") { auto S = "world"_fmt_res; - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res); } TEST_CASE("FORMAT a constexpr int argument", "[ct_format]") { + using expected_spans_t = stdx::type_list>; constexpr static auto I = 17; - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res); + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", I) == + stdx::make_format_result( + "Hello 17"_ctst, stdx::tuple{stdx::ct_format_arg{}})); } #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); + + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello {}"_ctst, stdx::tuple{S}); + + STATIC_CHECK(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); + + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello world"_ctst, + stdx::tuple{stdx::ct_format_arg{}}); + + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", S) == expected); } TEST_CASE("FORMAT an integral_constant argument", "[ct_format]") { auto I = std::integral_constant{}; - STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res); + + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello 17"_ctst, stdx::tuple{stdx::ct_format_arg{}}); + + STATIC_CHECK(STDX_CT_FORMAT("Hello {}", I) == expected); } #ifdef __clang__ @@ -346,8 +528,11 @@ struct expression_test { TEST_CASE("FORMAT non-constexpr expression", "[utility]") { auto x = 17; - constexpr auto expected = - stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(17)}; + + using expected_spans_t = stdx::type_list>; + constexpr auto expected = stdx::make_format_result( + "Hello {}"_ctst, stdx::tuple{17}); + CHECK(STDX_CT_FORMAT("Hello {}", expression_test{}.f(x)) == expected); } #endif