Skip to content
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ target_sources(
include/stdx/cx_set.hpp
include/stdx/cx_vector.hpp
include/stdx/detail/bitset_common.hpp
include/stdx/detail/fmt.hpp
include/stdx/detail/list_common.hpp
include/stdx/env.hpp
include/stdx/for_each_n_args.hpp
Expand Down
3 changes: 2 additions & 1 deletion docs/ct_format.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ provides `ct_format`, a compile-time function for formatting strings.
NOTE: Like xref:ct_string.adoc#_ct_string_hpp[`ct_string`], `ct_format` is
available only in C++20 and later.

IMPORTANT: `ct_format` is not yet available on freestanding implementations.
IMPORTANT: `ct_format` is limited on freestanding implementations: format
specifiers are limited to `{}` and `{:x}`.

The format string is provided as a template argument, and the arguments to be
formatted as regular function arguments.
Expand Down
3 changes: 2 additions & 1 deletion docs/static_assert.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

`STATIC_ASSERT` is a way to produce compile-time errors using formatted strings.

IMPORTANT: `STATIC_ASSERT` is not yet available on freestanding implementations.
IMPORTANT: `STATIC_ASSERT` is limited on freestanding implementations in the
same way as xref:ct_format.adoc#_ct_format_hpp[`ct_format`].

[source,cpp]
----
Expand Down
6 changes: 2 additions & 4 deletions include/stdx/ct_format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
#include <stdx/concepts.hpp>
#include <stdx/ct_conversions.hpp>
#include <stdx/ct_string.hpp>
#include <stdx/detail/fmt.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>
#include <fmt/format.h>

#include <algorithm>
#include <array>
#include <iterator>
Expand Down Expand Up @@ -207,7 +205,7 @@ CONSTEVAL auto perform_format(auto s, auto v) -> ct_string<N + 1> {

template <ct_string Fmt, typename Arg> constexpr auto format1(Arg arg) {
if constexpr (requires { arg_value(arg); }) {
constexpr auto fmtstr = FMT_COMPILE(std::string_view{Fmt});
constexpr auto fmtstr = STDX_FMT_COMPILE(Fmt);
constexpr auto a = arg_value(arg);
if constexpr (is_specialization_of_v<std::remove_cv_t<decltype(a)>,
format_result>) {
Expand Down
174 changes: 174 additions & 0 deletions include/stdx/detail/fmt.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
#pragma once

#if not STDX_FMT_FREESTANDING

#include <fmt/compile.h>
#include <fmt/format.h>

#include <string_view>

#define STDX_FMT_COMPILE(Fmt) FMT_COMPILE(std::string_view{Fmt})

#else

#include <stdx/compiler.hpp>
#include <stdx/concepts.hpp>
#include <stdx/ranges.hpp>

#include <cstddef>

#define STDX_FMT_COMPILE(X) [] { return X; }

namespace stdx {
inline namespace v1 {
namespace fmt {

namespace detail {
struct fmt_spec {
int len{};
int base{};
bool well_formed{true};
};

CONSTEVAL auto parse_fmt_spec(auto fmtstr) -> fmt_spec {
constexpr auto fmt = fmtstr();
auto i = fmt.begin();
while (*i != '{') {
++i;
}
++i;
if (*i == '}') {
return {.len = 2, .base = 10};
} else if (*i++ == ':' and *i++ == 'x' and *i == '}') {
return {.len = 4, .base = 16};
}
return {.well_formed = false};
}

template <stdx::range R>
CONSTEVAL auto formatted_size(fmt_spec, R const &r) -> std::size_t {
return r.size();
}

CONSTEVAL auto formatted_size(fmt_spec, char const *str) -> std::size_t {
std::size_t sz{};
while (*str) {
++sz;
++str;
}
return sz;
}

CONSTEVAL auto formatted_size(fmt_spec, char) -> std::size_t { return 1; }

template <stdx::integral I>
CONSTEVAL auto formatted_size(fmt_spec s, I i) -> std::size_t {
if (i == 0) {
return 1;
} else {
std::size_t sz{};
if (i < 0) {
++sz;
}

while (i != 0) {
++sz;
i /= s.base;
}
return sz;
}
}
} // namespace detail

CONSTEVAL auto formatted_size(auto fmtstr, auto v) -> std::size_t {
constexpr auto spec = detail::parse_fmt_spec(fmtstr);
static_assert(
spec.well_formed,
"Freestanding fmt implementation does not support that format "
"specifier");

return fmtstr().size() - spec.len + detail::formatted_size(spec, v);
}

namespace detail {
template <typename It, stdx::range R>
CONSTEVAL auto format_to(fmt_spec, It dest, R const &r) -> It {
for (auto const c : r) {
*dest++ = c;
}
return dest;
}

template <typename It>
CONSTEVAL auto format_to(fmt_spec, It dest, char const *str) -> It {
while (*str) {
*dest++ = *str++;
}
return dest;
}

template <typename It>
CONSTEVAL auto format_to(fmt_spec, It dest, char c) -> It {
*dest++ = c;
return dest;
}

template <typename It, stdx::integral I>
CONSTEVAL auto format_to(fmt_spec s, It dest, I i) -> It {
if (i == 0) {
*dest++ = '0';
} else {
auto abs = []<typename T>(T v) -> T { return v < 0 ? -v : v; };
auto to_digit = [](auto n) -> char { return "0123456789abcdef"[n]; };

if (i < 0) {
*dest++ = '-';
}

auto b = dest;
while (i != 0) {
auto digit = to_digit(abs(i % s.base));
i /= s.base;
*dest++ = digit;
}

auto e = dest - 1;
while (b < e) {
auto tmp = *b;
*b++ = *e;
*e-- = tmp;
}
}
return dest;
}

} // namespace detail

template <typename It>
CONSTEVAL auto format_to(It dest, auto fmtstr, auto v) -> void {
constexpr auto fmt = fmtstr();
constexpr auto spec = detail::parse_fmt_spec(fmtstr);

// copy to opening {
auto src = fmt.begin();
while (*src != '{') {
*dest++ = *src++;
}

// skip fmt spec
src += spec.len;

// format the value into the buffer
dest = detail::format_to(spec, dest, v);

// copy the rest of src
while (*src) {
*dest++ = *src++;
}
}

} // namespace fmt
} // namespace v1
} // namespace stdx

#endif
11 changes: 11 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
indexed_tuple
tuple
tuple_algorithms)

add_unit_test(
"ct_format_freestanding_test"
CATCH2
FILES
"ct_format.cpp"
LIBRARIES
warnings
stdx)
target_compile_definitions(ct_format_freestanding_test
PRIVATE -DSTDX_FMT_FREESTANDING=1)
endif()

add_subdirectory(fail)
Expand Down
35 changes: 35 additions & 0 deletions test/ct_format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

#include <catch2/catch_test_macros.hpp>

#include <array>
#include <limits>
#include <string_view>
#include <type_traits>

using namespace stdx::ct_string_literals;

TEST_CASE("detect string format specifiers", "[ct_format]") {
Expand Down Expand Up @@ -58,6 +63,29 @@ TEST_CASE("format a compile-time integral argument (CX_VALUE)", "[ct_format]") {
"Hello 42"_fmt_res);
}

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);
}

TEST_CASE("format most negative compile-time integral argument (CX_VALUE)",
"[ct_format]") {
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(
CX_VALUE(std::numeric_limits<int>::min())) ==
"Hello -2147483648"_fmt_res);
}

TEST_CASE("format zero (CX_VALUE)", "[ct_format]") {
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(CX_VALUE(0)) ==
"Hello 0"_fmt_res);
}

TEST_CASE("format a char (CX_VALUE)", "[ct_format]") {
STATIC_REQUIRE(stdx::ct_format<"Hello {}orld">(CX_VALUE('w')) ==
"Hello world"_fmt_res);
}

TEST_CASE("format a compile-time integral argument (ct)", "[ct_format]") {
STATIC_REQUIRE(stdx::ct_format<"Hello {}">(stdx::ct<42>()) ==
"Hello 42"_fmt_res);
Expand All @@ -73,10 +101,17 @@ TEST_CASE("format a type argument (ct)", "[ct_format]") {
"Hello int"_fmt_res);
}

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);
}

#if not STDX_FMT_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);
}
#endif

namespace {
enum struct E { A };
Expand Down
2 changes: 1 addition & 1 deletion usage_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ cpmaddpackage(NAME stdx SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/.." GIT_TAG HEAD)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE stdx)
if(CPP_IMPLEMENTATION STREQUAL "FREESTANDING")
target_compile_definitions(app PRIVATE SIMULATE_FREESTANDING)
target_compile_definitions(app PRIVATE STDX_FMT_FREESTANDING)
target_compile_options(app PRIVATE -ffreestanding)
endif()
4 changes: 0 additions & 4 deletions usage_test/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
#include <stdx/compiler.hpp>
#include <stdx/concepts.hpp>
#include <stdx/ct_conversions.hpp>
#ifndef SIMULATE_FREESTANDING
#include <stdx/ct_format.hpp>
#endif
#include <stdx/ct_string.hpp>
#include <stdx/cx_map.hpp>
#include <stdx/cx_multimap.hpp>
Expand All @@ -35,9 +33,7 @@
#include <stdx/ranges.hpp>
#include <stdx/rollover.hpp>
#include <stdx/span.hpp>
#ifndef SIMULATE_FREESTANDING
#include <stdx/static_assert.hpp>
#endif
#include <stdx/tuple.hpp>
#include <stdx/tuple_algorithms.hpp>
#include <stdx/tuple_destructure.hpp>
Expand Down