Skip to content

Commit dfc27dd

Browse files
authored
Merge pull request #183 from elbeno/cts-preserve-constexpr
🎨 Preserve `ct_format` string types
2 parents 25911ff + 1180eb1 commit dfc27dd

File tree

7 files changed

+173
-69
lines changed

7 files changed

+173
-69
lines changed

include/stdx/ct_format.hpp

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,24 @@ namespace stdx {
3232
inline namespace v1 {
3333
template <typename Str, typename Args> struct format_result {
3434
[[no_unique_address]] Str str;
35-
[[no_unique_address]] Args args;
35+
[[no_unique_address]] Args args{};
3636

3737
private:
3838
friend constexpr auto operator==(format_result const &,
3939
format_result const &) -> bool = default;
4040
};
41+
4142
template <typename Str, typename Args>
4243
format_result(Str, Args) -> format_result<Str, Args>;
44+
template <typename Str> format_result(Str) -> format_result<Str, tuple<>>;
45+
46+
inline namespace literals {
47+
inline namespace ct_string_literals {
48+
template <ct_string S> CONSTEVAL auto operator""_fmt_res() {
49+
return format_result{cts_t<S>{}};
50+
}
51+
} // namespace ct_string_literals
52+
} // namespace literals
4353

4454
namespace detail {
4555
template <typename It> CONSTEVAL auto find_spec(It first, It last) -> It {
@@ -90,19 +100,30 @@ template <typename T>
90100
concept cx_value = requires { typename T::cx_value_t; } or
91101
requires(T t) { ct_string_from_type(t); };
92102

93-
CONSTEVAL auto arg_value(auto a) { return a; }
103+
template <typename T, T V>
104+
CONSTEVAL auto arg_value(std::integral_constant<T, V>) {
105+
if constexpr (std::is_enum_v<T>) {
106+
return enum_as_string<V>();
107+
} else {
108+
return V;
109+
}
110+
}
94111

95112
template <typename T> CONSTEVAL auto arg_value(type_identity<T>) {
96113
return type_as_string<T>();
97114
}
98115

116+
template <ct_string S> CONSTEVAL auto arg_value(cts_t<S>) { return S; }
117+
99118
CONSTEVAL auto arg_value(cx_value auto a) {
100119
if constexpr (requires { ct_string_from_type(a); }) {
101120
return ct_string_from_type(a);
102121
} else if constexpr (std::is_enum_v<decltype(a())>) {
103122
return enum_as_string<a()>();
104-
} else {
123+
} else if constexpr (requires { arg_value(a()); }) {
105124
return arg_value(a());
125+
} else {
126+
return a();
106127
}
107128
}
108129

@@ -131,51 +152,44 @@ CONSTEVAL auto convert_input(auto s) {
131152
if constexpr (requires { ct_string_from_type(s); }) {
132153
return ct_string_from_type(s);
133154
} else {
134-
return s;
155+
return s.value;
135156
}
136157
}
137158

138159
template <ct_string S,
139160
template <typename T, T...> typename Output = detail::null_output>
140161
CONSTEVAL auto convert_output() {
141162
if constexpr (same_as<Output<char>, null_output<char>>) {
142-
return S;
163+
return cts_t<S>{};
143164
} else {
144165
return ct_string_to_type<S, Output>();
145166
}
146167
}
147168

148-
template <ct_string Fmt,
149-
template <typename T, T...> typename Output = detail::null_output,
150-
typename Arg>
151-
constexpr auto format1(Arg arg) {
152-
if constexpr (cx_value<Arg>) {
153-
constexpr auto result = [&] {
169+
template <ct_string Fmt, typename Arg> constexpr auto format1(Arg arg) {
170+
if constexpr (requires { arg_value(arg); }) {
171+
return [&] {
154172
constexpr auto fmtstr = FMT_COMPILE(std::string_view{Fmt});
155173
constexpr auto a = arg_value(arg);
174+
auto const f = []<std::size_t N>(auto s, auto v) {
175+
ct_string<N + 1> cts{};
176+
fmt::format_to(cts.begin(), s, v);
177+
return cts;
178+
};
156179
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(a)>,
157180
format_result>) {
158181
constexpr auto s = convert_input(a.str);
159182
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};
183+
constexpr auto cts = f.template operator()<sz>(fmtstr, s);
184+
return format_result{cts_t<cts>{}, a.args};
163185
} else {
164186
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;
187+
constexpr auto cts = f.template operator()<sz>(fmtstr, a);
188+
return format_result{cts_t<cts>{}};
168189
}
169190
}();
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-
}
177191
} else {
178-
return format_result{convert_output<Fmt, Output>(), tuple{arg}};
192+
return format_result{cts_t<Fmt>{}, tuple{arg}};
179193
}
180194
}
181195

@@ -188,10 +202,9 @@ concept ct_format_compatible = requires {
188202

189203
template <ct_string Fmt> struct fmt_data {
190204
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]);
205+
constexpr static auto N = count_specifiers(fmt);
206+
constexpr static auto splits = split_specifiers<N + 1>(fmt);
207+
constexpr static auto last_cts = to_ct_string<splits[N].size()>(splits[N]);
195208
};
196209
} // namespace detail
197210

@@ -207,13 +220,15 @@ constexpr auto ct_format = [](auto &&...args) {
207220
[[maybe_unused]] auto const format1 = [&]<std::size_t I>(auto &&arg) {
208221
constexpr auto cts =
209222
detail::to_ct_string<data::splits[I].size()>(data::splits[I]);
210-
return detail::format1<cts, Output>(FWD(arg));
223+
return detail::format1<cts>(FWD(arg));
211224
};
212225

213-
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
226+
auto const result = [&]<std::size_t... Is>(std::index_sequence<Is...>) {
214227
return (format1.template operator()<Is>(FWD(args)) + ... +
215-
detail::convert_output<data::last_cts, Output>());
228+
format_result{cts_t<data::last_cts>{}});
216229
}(std::make_index_sequence<data::N>{});
230+
return format_result{detail::convert_output<result.str.value, Output>(),
231+
result.args};
217232
};
218233
} // namespace v1
219234
} // namespace stdx

include/stdx/ct_string.hpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,6 @@ template <std::size_t N> struct ct_string {
5858
std::array<char, N> value{};
5959
};
6060

61-
template <stdx::ct_string S> struct cts_t {
62-
constexpr static auto value = S;
63-
};
64-
6561
template <std::size_t N, std::size_t M>
6662
[[nodiscard]] constexpr auto operator==(ct_string<N> const &lhs,
6763
ct_string<M> const &rhs) -> bool {
@@ -118,9 +114,27 @@ operator+(ct_string<N> const &lhs,
118114
return ret;
119115
}
120116

117+
template <ct_string S> struct cts_t {
118+
constexpr static auto value = S;
119+
};
120+
121+
template <ct_string X, ct_string Y>
122+
constexpr auto operator==(cts_t<X>, cts_t<Y>) -> bool {
123+
return X == Y;
124+
}
125+
126+
template <ct_string X, ct_string Y>
127+
constexpr auto operator+(cts_t<X>, cts_t<Y>) {
128+
return cts_t<X + Y>{};
129+
}
130+
131+
template <ct_string Value> CONSTEVAL auto ct() { return cts_t<Value>{}; }
132+
121133
inline namespace literals {
122134
inline namespace ct_string_literals {
123135
template <ct_string S> CONSTEVAL auto operator""_cts() { return S; }
136+
137+
template <ct_string S> CONSTEVAL auto operator""_ctst() { return cts_t<S>{}; }
124138
} // namespace ct_string_literals
125139
} // namespace literals
126140
} // namespace v1

include/stdx/static_assert.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ 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_format<Fmt>(make_ct.template operator()<Args>()...).str.value;
3535
}
3636
} // namespace detail
3737

include/stdx/utility.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ constexpr auto is_aligned_with = [](auto v) -> bool {
192192
return (static_cast<std::uintptr_t>(v) & mask) == 0;
193193
}
194194
};
195+
196+
template <auto Value> CONSTEVAL auto ct() {
197+
return std::integral_constant<decltype(Value), Value>{};
198+
}
199+
template <typename T> CONSTEVAL auto ct() { return type_identity<T>{}; }
195200
} // namespace v1
196201
} // namespace stdx
197202

test/ct_format.cpp

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -31,94 +31,121 @@ 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

37-
TEST_CASE("format a compile-time stringish argument", "[ct_format]") {
37+
TEST_CASE("format a compile-time stringish argument (CX_VALUE)",
38+
"[ct_format]") {
3839
using namespace std::string_view_literals;
3940
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world"sv)) ==
40-
"Hello world"_cts);
41+
"Hello world"_fmt_res);
4142
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world"_cts)) ==
42-
"Hello world"_cts);
43+
"Hello world"_fmt_res);
4344
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE("world")) ==
44-
"Hello world"_cts);
45+
"Hello world"_fmt_res);
46+
}
47+
48+
TEST_CASE("format a compile-time stringish argument (ct)", "[ct_format]") {
49+
using namespace std::string_view_literals;
50+
static_assert(stdx::ct_format<"Hello {}">("world"_ctst) ==
51+
"Hello world"_fmt_res);
52+
static_assert(stdx::ct_format<"Hello {}">(stdx::ct<"world">()) ==
53+
"Hello world"_fmt_res);
4554
}
4655

47-
TEST_CASE("format a compile-time integral argument", "[ct_format]") {
48-
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(42)) == "Hello 42"_cts);
56+
TEST_CASE("format a compile-time integral argument (CX_VALUE)", "[ct_format]") {
57+
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(42)) ==
58+
"Hello 42"_fmt_res);
4959
}
5060

51-
TEST_CASE("format a type argument", "[ct_format]") {
61+
TEST_CASE("format a compile-time integral argument (ct)", "[ct_format]") {
62+
static_assert(stdx::ct_format<"Hello {}">(stdx::ct<42>()) ==
63+
"Hello 42"_fmt_res);
64+
}
65+
66+
TEST_CASE("format a type argument (CX_VALUE)", "[ct_format]") {
5267
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(int)) ==
53-
"Hello int"_cts);
68+
"Hello int"_fmt_res);
69+
}
70+
71+
TEST_CASE("format a type argument (ct)", "[ct_format]") {
72+
static_assert(stdx::ct_format<"Hello {}">(stdx::ct<int>()) ==
73+
"Hello int"_fmt_res);
5474
}
5575

5676
TEST_CASE("format a compile-time argument with fmt spec", "[ct_format]") {
5777
static_assert(stdx::ct_format<"Hello {:*>#6x}">(CX_VALUE(42)) ==
58-
"Hello **0x2a"_cts);
78+
"Hello **0x2a"_fmt_res);
5979
}
6080

6181
namespace {
6282
enum struct E { A };
6383
}
6484

65-
TEST_CASE("format a compile-time enum argument", "[ct_format]") {
66-
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) == "Hello A"_cts);
85+
TEST_CASE("format a compile-time enum argument (CX_VALUE)", "[ct_format]") {
86+
static_assert(stdx::ct_format<"Hello {}">(CX_VALUE(E::A)) ==
87+
"Hello A"_fmt_res);
88+
}
89+
90+
TEST_CASE("format a compile-time enum argument (ct)", "[ct_format]") {
91+
static_assert(stdx::ct_format<"Hello {}">(stdx::ct<E::A>()) ==
92+
"Hello A"_fmt_res);
6793
}
6894

6995
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)});
96+
constexpr auto x = 17;
97+
constexpr auto expected =
98+
stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(x)};
99+
100+
CHECK(stdx::ct_format<"Hello {}">(x) == expected);
101+
static_assert(stdx::ct_format<"Hello {}">(x) == expected);
75102
}
76103

77104
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)});
105+
constexpr auto x = 17;
106+
constexpr auto expected =
107+
stdx::format_result{"Hello int {}"_ctst, stdx::make_tuple(x)};
108+
109+
CHECK(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected);
110+
static_assert(stdx::ct_format<"Hello {} {}">(CX_VALUE(int), x) == expected);
84111
}
85112

86113
TEST_CASE("format a compile-time and a runtime argument (2)", "[ct_format]") {
87114
static_assert(
88115
stdx::ct_format<"Hello {} {}">(42, CX_VALUE(int)) ==
89-
stdx::format_result{"Hello {} int"_cts, stdx::make_tuple(42)});
116+
stdx::format_result{"Hello {} int"_ctst, stdx::make_tuple(42)});
90117
}
91118

92119
TEST_CASE("format multiple runtime arguments", "[ct_format]") {
93120
static_assert(
94121
stdx::ct_format<"Hello {} {}">(42, 17) ==
95-
stdx::format_result{"Hello {} {}"_cts, stdx::make_tuple(42, 17)});
122+
stdx::format_result{"Hello {} {}"_ctst, stdx::make_tuple(42, 17)});
96123
}
97124

98125
TEST_CASE("format multiple mixed arguments", "[ct_format]") {
99126
using namespace std::string_view_literals;
100127
auto b = "B"sv;
101128
CHECK(stdx::ct_format<"Hello {} {} {} {} world">(42, CX_VALUE("A"sv), b,
102129
CX_VALUE(int)) ==
103-
stdx::format_result{"Hello {} A {} int world"_cts,
130+
stdx::format_result{"Hello {} A {} int world"_ctst,
104131
stdx::make_tuple(42, "B"sv)});
105132
static_assert(stdx::ct_format<"Hello {} {} {} {} world">(
106133
42, CX_VALUE("A"sv), "B"sv, CX_VALUE(int)) ==
107-
stdx::format_result{"Hello {} A {} int world"_cts,
134+
stdx::format_result{"Hello {} A {} int world"_ctst,
108135
stdx::make_tuple(42, "B"sv)});
109136
}
110137

111138
TEST_CASE("format a formatted string", "[ct_format]") {
112139
static_assert(stdx::ct_format<"The value is {}.">(
113140
CX_VALUE(stdx::ct_format<"(year={})">(2022))) ==
114-
stdx::format_result{"The value is (year={})."_cts,
141+
stdx::format_result{"The value is (year={})."_ctst,
115142
stdx::make_tuple(2022)});
116143
}
117144

118145
TEST_CASE("format a ct-formatted string", "[ct_format]") {
119146
constexpr static auto cts = stdx::ct_format<"(year={})">(CX_VALUE(2024));
120147
static_assert(stdx::ct_format<"The value is {}.">(CX_VALUE(cts)) ==
121-
"The value is (year=2024)."_cts);
148+
"The value is (year=2024)."_fmt_res);
122149
}
123150

124151
namespace {
@@ -139,7 +166,7 @@ template <class T, T... Ls, T... Rs>
139166
TEST_CASE("format_to a different type", "[ct_format]") {
140167
using namespace std::string_view_literals;
141168
static_assert(stdx::ct_format<"{}", string_constant>(CX_VALUE("A"sv)) ==
142-
string_constant<char, 'A'>{});
169+
stdx::format_result{string_constant<char, 'A'>{}});
143170

144171
auto x = 17;
145172
CHECK(stdx::ct_format<"{}", string_constant>(x) ==
@@ -152,7 +179,7 @@ TEST_CASE("format_to a different type", "[ct_format]") {
152179

153180
TEST_CASE("format a string-type argument", "[ct_format]") {
154181
static_assert(stdx::ct_format<"Hello {}!">(string_constant<char, 'A'>{}) ==
155-
"Hello A!"_cts);
182+
"Hello A!"_fmt_res);
156183
}
157184

158185
TEST_CASE("format a formatted string with different type", "[ct_format]") {
@@ -168,7 +195,8 @@ TEST_CASE("format a ct-formatted string with different type", "[ct_format]") {
168195
stdx::ct_format<"B{}C", string_constant>(CX_VALUE(2024));
169196
static_assert(
170197
stdx::ct_format<"A{}D", string_constant>(CX_VALUE(cts)) ==
171-
string_constant<char, 'A', 'B', '2', '0', '2', '4', 'C', 'D'>{});
198+
stdx::format_result{
199+
string_constant<char, 'A', 'B', '2', '0', '2', '4', 'C', 'D'>{}});
172200
}
173201

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

0 commit comments

Comments
 (0)