Skip to content

Commit d7d1c07

Browse files
committed
🎨 Preserve ct_format string types
Problem: - `ct_format` produces a type that is either a `ct_string` or a `format_result`, which means all client code needs to check that. - `ct_format` works at compile-time but produces a string value that may lose its `constexpr` nature, so can't be put into a type. Solution: - Always return a `format_result` (possibly with an empty tuple). - Wrap the returned `ct_string` in a type.
1 parent 25911ff commit d7d1c07

File tree

3 files changed

+86
-58
lines changed

3 files changed

+86
-58
lines changed

include/stdx/ct_format.hpp

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,44 @@ struct fmt::formatter<stdx::ct_string<N>> : fmt::formatter<std::string_view> {
3030

3131
namespace stdx {
3232
inline namespace v1 {
33+
template <ct_string S> struct ct_string_t {};
34+
template <typename> constexpr auto ct_string_v = ct_string<1>{};
35+
template <ct_string S> constexpr auto ct_string_v<ct_string_t<S>> = S;
36+
37+
template <ct_string X, ct_string Y>
38+
constexpr auto operator==(ct_string_t<X>, ct_string_t<Y>) -> bool {
39+
return X == Y;
40+
}
41+
42+
template <ct_string X, ct_string Y>
43+
constexpr auto operator+(ct_string_t<X>, ct_string_t<Y>) {
44+
return ct_string_t<X + Y>{};
45+
}
46+
3347
template <typename Str, typename Args> struct format_result {
3448
[[no_unique_address]] Str str;
35-
[[no_unique_address]] Args args;
49+
[[no_unique_address]] Args args{};
3650

3751
private:
3852
friend constexpr auto operator==(format_result const &,
3953
format_result const &) -> bool = default;
4054
};
55+
4156
template <typename Str, typename Args>
4257
format_result(Str, Args) -> format_result<Str, Args>;
58+
template <typename Str> format_result(Str) -> format_result<Str, tuple<>>;
59+
60+
inline namespace literals {
61+
inline namespace ct_string_literals {
62+
template <ct_string S> CONSTEVAL auto operator""_fmt_res() {
63+
return format_result{ct_string_t<S>{}};
64+
}
65+
66+
template <ct_string S> CONSTEVAL auto operator""_ctst() {
67+
return ct_string_t<S>{};
68+
}
69+
} // namespace ct_string_literals
70+
} // namespace literals
4371

4472
namespace detail {
4573
template <typename It> CONSTEVAL auto find_spec(It first, It last) -> It {
@@ -131,51 +159,44 @@ CONSTEVAL auto convert_input(auto s) {
131159
if constexpr (requires { ct_string_from_type(s); }) {
132160
return ct_string_from_type(s);
133161
} else {
134-
return s;
162+
return ct_string_v<decltype(s)>;
135163
}
136164
}
137165

138166
template <ct_string S,
139167
template <typename T, T...> typename Output = detail::null_output>
140168
CONSTEVAL auto convert_output() {
141169
if constexpr (same_as<Output<char>, null_output<char>>) {
142-
return S;
170+
return ct_string_t<S>{};
143171
} else {
144172
return ct_string_to_type<S, Output>();
145173
}
146174
}
147175

148-
template <ct_string Fmt,
149-
template <typename T, T...> typename Output = detail::null_output,
150-
typename Arg>
151-
constexpr auto format1(Arg arg) {
176+
template <ct_string Fmt, typename Arg> constexpr auto format1(Arg arg) {
152177
if constexpr (cx_value<Arg>) {
153-
constexpr auto result = [&] {
178+
return [&] {
154179
constexpr auto fmtstr = FMT_COMPILE(std::string_view{Fmt});
155180
constexpr auto a = arg_value(arg);
181+
auto const f = []<std::size_t N>(auto s, auto v) {
182+
ct_string<N + 1> cts{};
183+
fmt::format_to(cts.begin(), s, v);
184+
return cts;
185+
};
156186
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(a)>,
157187
format_result>) {
158188
constexpr auto s = convert_input(a.str);
159189
constexpr auto sz = fmt::formatted_size(fmtstr, s);
160-
ct_string<sz + 1> cts{};
161-
fmt::format_to(cts.begin(), fmtstr, s);
162-
return format_result{cts, a.args};
190+
constexpr auto cts = f.template operator()<sz>(fmtstr, s);
191+
return format_result{ct_string_t<cts>{}, a.args};
163192
} else {
164193
constexpr auto sz = fmt::formatted_size(fmtstr, a);
165-
ct_string<sz + 1> cts{};
166-
fmt::format_to(cts.begin(), fmtstr, a);
167-
return cts;
194+
constexpr auto cts = f.template operator()<sz>(fmtstr, a);
195+
return format_result{ct_string_t<cts>{}};
168196
}
169197
}();
170-
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(result)>,
171-
format_result>) {
172-
return format_result{convert_output<result.str, Output>(),
173-
result.args};
174-
} else {
175-
return convert_output<result, Output>();
176-
}
177198
} else {
178-
return format_result{convert_output<Fmt, Output>(), tuple{arg}};
199+
return format_result{ct_string_t<Fmt>{}, tuple{arg}};
179200
}
180201
}
181202

@@ -188,10 +209,9 @@ concept ct_format_compatible = requires {
188209

189210
template <ct_string Fmt> struct fmt_data {
190211
constexpr static auto fmt = std::string_view{Fmt};
191-
constexpr static auto N = detail::count_specifiers(fmt);
192-
constexpr static auto splits = detail::split_specifiers<N + 1>(fmt);
193-
constexpr static auto last_cts =
194-
detail::to_ct_string<splits[N].size()>(splits[N]);
212+
constexpr static auto N = count_specifiers(fmt);
213+
constexpr static auto splits = split_specifiers<N + 1>(fmt);
214+
constexpr static auto last_cts = to_ct_string<splits[N].size()>(splits[N]);
195215
};
196216
} // namespace detail
197217

@@ -207,13 +227,16 @@ constexpr auto ct_format = [](auto &&...args) {
207227
[[maybe_unused]] auto const format1 = [&]<std::size_t I>(auto &&arg) {
208228
constexpr auto cts =
209229
detail::to_ct_string<data::splits[I].size()>(data::splits[I]);
210-
return detail::format1<cts, Output>(FWD(arg));
230+
return detail::format1<cts>(FWD(arg));
211231
};
212232

213-
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
233+
auto const result = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
214234
return (format1.template operator()<Is>(FWD(args)) + ... +
215-
detail::convert_output<data::last_cts, Output>());
235+
format_result{ct_string_t<data::last_cts>{}});
216236
}(std::make_index_sequence<data::N>{});
237+
return format_result{
238+
detail::convert_output<ct_string_v<decltype(result.str)>, Output>(),
239+
result.args};
217240
};
218241
} // namespace v1
219242
} // namespace stdx

include/stdx/static_assert.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ template <ct_string Fmt, auto... Args> constexpr auto static_format() {
3131
return CX_VALUE(V);
3232
}
3333
};
34-
return ct_format<Fmt>(make_ct.template operator()<Args>()...);
34+
return ct_string_v<
35+
decltype(ct_format<Fmt>(make_ct.template operator()<Args>()...).str)>;
3536
}
3637
} // namespace detail
3738

test/ct_format.cpp

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,94 +31,97 @@ TEST_CASE("split format string by specifiers", "[ct_format]") {
3131
}
3232

3333
TEST_CASE("format a static string", "[ct_format]") {
34-
static_assert(stdx::ct_format<"Hello">() == "Hello"_cts);
34+
static_assert(stdx::ct_format<"Hello">() == "Hello"_fmt_res);
3535
}
3636

3737
TEST_CASE("format a compile-time stringish argument", "[ct_format]") {
3838
using namespace std::string_view_literals;
3939
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world"sv)) ==
40-
"Hello world"_cts);
40+
"Hello world"_fmt_res);
4141
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world"_cts)) ==
42-
"Hello world"_cts);
42+
"Hello world"_fmt_res);
4343
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world")) ==
44-
"Hello world"_cts);
44+
"Hello world"_fmt_res);
4545
}
4646

4747
TEST_CASE("format a compile-time integral argument", "[ct_format]") {
48-
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(42)) == "Hello 42"_cts);
48+
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(42)) ==
49+
"Hello 42"_fmt_res);
4950
}
5051

5152
TEST_CASE("format a type argument", "[ct_format]") {
5253
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(int)) ==
53-
"Hello int"_cts);
54+
"Hello int"_fmt_res);
5455
}
5556

5657
TEST_CASE("format a compile-time argument with fmt spec", "[ct_format]") {
5758
static_assert(stdx::ct_format<"Hello {:*>#6x}">(CX_VALUE(42)) ==
58-
"Hello **0x2a"_cts);
59+
"Hello **0x2a"_fmt_res);
5960
}
6061

6162
namespace {
6263
enum struct E { A };
6364
}
6465

6566
TEST_CASE("format a compile-time enum argument", "[ct_format]") {
66-
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) == "Hello A"_cts);
67+
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) ==
68+
"Hello A"_fmt_res);
6769
}
6870

6971
TEST_CASE("format a runtime argument", "[ct_format]") {
70-
auto x = 17;
71-
CHECK(stdx::ct_format<"Hello {}">(x) ==
72-
stdx::format_result{"Hello {}"_cts, stdx::make_tuple(17)});
73-
static_assert(stdx::ct_format<"Hello {}">(17) ==
74-
stdx::format_result{"Hello {}"_cts, stdx::make_tuple(17)});
72+
constexpr auto x = 17;
73+
constexpr auto expected =
74+
stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(x)};
75+
76+
CHECK(stdx::ct_format<"Hello {}">(x) == expected);
77+
static_assert(stdx::ct_format<"Hello {}">(x) == expected);
7578
}
7679

7780
TEST_CASE("format a compile-time and a runtime argument (1)", "[ct_format]") {
78-
auto x = 17;
79-
CHECK(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) ==
80-
stdx::format_result{"Hello int {}"_cts, stdx::make_tuple(17)});
81-
static_assert(
82-
stdx::ct_format<"Hello {} {}">(CX_VALUE(int), 17) ==
83-
stdx::format_result{"Hello int {}"_cts, stdx::make_tuple(17)});
81+
constexpr auto x = 17;
82+
constexpr auto expected =
83+
stdx::format_result{"Hello int {}"_ctst, stdx::make_tuple(x)};
84+
85+
CHECK(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected);
86+
static_assert(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected);
8487
}
8588

8689
TEST_CASE("format a compile-time and a runtime argument (2)", "[ct_format]") {
8790
static_assert(
8891
stdx::ct_format<"Hello {} {}">(42, CX_VALUE(int)) ==
89-
stdx::format_result{"Hello {} int"_cts, stdx::make_tuple(42)});
92+
stdx::format_result{"Hello {} int"_ctst, stdx::make_tuple(42)});
9093
}
9194

9295
TEST_CASE("format multiple runtime arguments", "[ct_format]") {
9396
static_assert(
9497
stdx::ct_format<"Hello {} {}">(42, 17) ==
95-
stdx::format_result{"Hello {} {}"_cts, stdx::make_tuple(42, 17)});
98+
stdx::format_result{"Hello {} {}"_ctst, stdx::make_tuple(42, 17)});
9699
}
97100

98101
TEST_CASE("format multiple mixed arguments", "[ct_format]") {
99102
using namespace std::string_view_literals;
100103
auto b = "B"sv;
101104
CHECK(stdx::ct_format<"Hello {} {} {} {} world">(42, CX_VALUE("A"sv), b,
102105
CX_VALUE(int)) ==
103-
stdx::format_result{"Hello {} A {} int world"_cts,
106+
stdx::format_result{"Hello {} A {} int world"_ctst,
104107
stdx::make_tuple(42, "B"sv)});
105108
static_assert(stdx::ct_format<"Hello {} {} {} {} world">(
106109
42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) ==
107-
stdx::format_result{"Hello {} A {} int world"_cts,
110+
stdx::format_result{"Hello {} A {} int world"_ctst,
108111
stdx::make_tuple(42, "B"sv)});
109112
}
110113

111114
TEST_CASE("format a formatted string", "[ct_format]") {
112115
static_assert(stdx::ct_format<"The value is {}.">(
113116
CX_VALUE(stdx::ct_format<"(year={})">(2022))) ==
114-
stdx::format_result{"The value is (year={})."_cts,
117+
stdx::format_result{"The value is (year={})."_ctst,
115118
stdx::make_tuple(2022)});
116119
}
117120

118121
TEST_CASE("format a ct-formatted string", "[ct_format]") {
119122
constexpr static auto cts = stdx::ct_format<"(year={})">(CX_VALUE(2024));
120123
static_assert(stdx::ct_format<"The value is {}.">(CX_VALUE(cts)) ==
121-
"The value is (year=2024)."_cts);
124+
"The value is (year=2024)."_fmt_res);
122125
}
123126

124127
namespace {
@@ -139,7 +142,7 @@ template <class T, T... Ls, T... Rs>
139142
TEST_CASE("format_to a different type", "[ct_format]") {
140143
using namespace std::string_view_literals;
141144
static_assert(stdx::ct_format<"{}", string_constant>(CX_VALUE("A"sv)) ==
142-
string_constant<char, 'A'>{});
145+
stdx::format_result{string_constant<char, 'A'>{}});
143146

144147
auto x = 17;
145148
CHECK(stdx::ct_format<"{}", string_constant>(x) ==
@@ -152,7 +155,7 @@ TEST_CASE("format_to a different type", "[ct_format]") {
152155

153156
TEST_CASE("format a string-type argument", "[ct_format]") {
154157
static_assert(stdx::ct_format<"Hello {}!">(string_constant<char, 'A'>{}) ==
155-
"Hello A!"_cts);
158+
"Hello A!"_fmt_res);
156159
}
157160

158161
TEST_CASE("format a formatted string with different type", "[ct_format]") {
@@ -168,7 +171,8 @@ TEST_CASE("format a ct-formatted string with different type", "[ct_format]") {
168171
stdx::ct_format<"B{}C", string_constant>(CX_VALUE(2024));
169172
static_assert(
170173
stdx::ct_format<"A{}D", string_constant>(CX_VALUE(cts)) ==
171-
string_constant<char, 'A', 'B', '2', '0', '2', '4', 'C', 'D'>{});
174+
stdx::format_result{
175+
string_constant<char, 'A', 'B', '2', '0', '2', '4', 'C', 'D'>{}});
172176
}
173177

174178
TEST_CASE("format multiple mixed arguments with different type",

0 commit comments

Comments
 (0)