Skip to content

✨ Add CT_WRAP #243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
15 changes: 12 additions & 3 deletions include/stdx/ct_format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include <stdx/concepts.hpp>
#include <stdx/ct_conversions.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/pp_map.hpp>
#include <stdx/tuple.hpp>
#include <stdx/tuple_algorithms.hpp>
#include <stdx/type_traits.hpp>
#include <stdx/utility.hpp>

#include <fmt/compile.h>
Expand Down Expand Up @@ -107,8 +109,8 @@ template <std::size_t N> CONSTEVAL auto split_specifiers(std::string_view fmt) {
}

template <typename T>
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<T> or requires(T t) { ct_string_from_type(t); };

template <typename T, T V>
CONSTEVAL auto arg_value(std::integral_constant<T, V>) {
Expand All @@ -125,7 +127,7 @@ template <typename T> CONSTEVAL auto arg_value(type_identity<T>) {

template <ct_string S> CONSTEVAL auto arg_value(cts_t<S>) { 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<decltype(a())>) {
Expand Down Expand Up @@ -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<S>(STDX_MAP(CX_WRAP __VA_OPT__(, ) __VA_ARGS__))

// NOLINTEND(cppcoreguidelines-macro-usage)

#endif
2 changes: 2 additions & 0 deletions include/stdx/ct_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ template <std::size_t N> struct ct_helper<ct_string<N>>;

template <ct_string Value> CONSTEVAL auto ct() { return cts_t<Value>{}; }

template <ct_string Value> constexpr auto is_ct_v<cts_t<Value>> = true;

inline namespace literals {
inline namespace ct_string_literals {
template <ct_string S> CONSTEVAL_UDL auto operator""_cts() { return S; }
Expand Down
39 changes: 39 additions & 0 deletions include/stdx/pp_map.hpp
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion include/stdx/static_assert.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ template <bool B> constexpr auto ct_check = ct_check_t<B>{};
namespace detail {
template <ct_string Fmt, auto... Args> constexpr auto static_format() {
constexpr auto make_ct = []<auto V>() {
if constexpr (cx_value<decltype(V)>) {
if constexpr (fmt_cx_value<decltype(V)>) {
return V;
} else {
return CX_VALUE(V);
Expand Down
109 changes: 93 additions & 16 deletions include/stdx/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,18 @@ struct from_any {
struct type_val {
template <typename T, typename U,
typename = std::enable_if_t<same_as_unqualified<type_val, U>>>
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 <typename T> 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<T>) {
return T{};
} else {
extern auto cxv_type_val_get_t(T *) -> T;
return cxv_type_val_get_t(nullptr);
}
}
};

Expand Down Expand Up @@ -202,13 +206,22 @@ template <typename T> struct ct_helper {
T value;
};
template <typename T> ct_helper(T) -> ct_helper<T>;

template <auto> CONSTEVAL auto cx_detect0() {}
CONSTEVAL auto cx_detect1(auto) { return 0; }
} // namespace detail

template <detail::ct_helper Value> CONSTEVAL auto ct() {
return std::integral_constant<decltype(Value.value), Value.value>{};
}
template <typename T> CONSTEVAL auto ct() { return type_identity<T>{}; }

template <typename> constexpr auto is_ct_v = false;
template <typename T, T V>
constexpr auto is_ct_v<std::integral_constant<T, V>> = true;
template <typename T> constexpr auto is_ct_v<type_identity<T>> = true;
template <typename T> constexpr auto is_ct_v<T const> = is_ct_v<T>;

#endif
} // namespace v1
} // namespace stdx
Expand All @@ -221,28 +234,92 @@ template <typename T> CONSTEVAL auto ct() { return type_identity<T>{}; }

#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<decltype(f())>) { \
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<decltype(CX_VALUE(X))>
#else
namespace stdx {
inline namespace v1 {
template <auto> 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) \
[&]<typename F>(F f) { \
STDX_PRAGMA(diagnostic push) \
STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \
if constexpr (::stdx::is_cx_value_v<std::invoke_result_t<F>>) { \
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)
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_tests(
optional
overload
panic
pp_map
priority
ranges
remove_cvref
Expand Down
50 changes: 48 additions & 2 deletions test/ct_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, 17>{};
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res);
}
27 changes: 27 additions & 0 deletions test/ct_string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<decltype(v1)>);
}

TEST_CASE("CT_WRAP", "[ct_string]") {
using namespace stdx::ct_string_literals;
auto x1 = "hello"_cts;
STATIC_REQUIRE(std::is_same_v<decltype(CT_WRAP(x1)), stdx::ct_string<6>>);
CHECK(CT_WRAP(x1) == "hello"_cts);

auto x2 = "hello"_ctst;
STATIC_REQUIRE(std::is_same_v<decltype(CT_WRAP(x2)), stdx::cts_t<"hello">>);
STATIC_REQUIRE(CT_WRAP(x2) == "hello"_ctst);

constexpr static auto x3 = "hello"_cts;
STATIC_REQUIRE(std::is_same_v<decltype(CT_WRAP(x3)), stdx::cts_t<"hello">>);
STATIC_REQUIRE(CT_WRAP(x3) == "hello"_ctst);

[]<stdx::ct_string X>() {
STATIC_REQUIRE(
std::is_same_v<decltype(CT_WRAP(X)), stdx::cts_t<"hello">>);
STATIC_REQUIRE(CT_WRAP(X) == "hello"_ctst);
}.template operator()<"hello">();
}
Loading