Skip to content
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

Add support of most format_specs for formatting at compile-time #2056

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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
4 changes: 2 additions & 2 deletions include/fmt/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -466,10 +466,10 @@ struct is_compiled_format<field<Char, T, N>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
using char_type = Char;
mutable formatter<T, Char> fmt;
formatter<T, Char> fmt;

template <typename OutputIt, typename... Args>
OutputIt format(OutputIt out, const Args&... args) const {
constexpr OutputIt format(OutputIt out, const Args&... args) const {
// This ensures that the argument type is convertile to `const T&`.
const T& arg = get<N>(args...);
const auto& vargs =
Expand Down
83 changes: 49 additions & 34 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,14 @@ struct monostate {};

namespace detail {

constexpr bool is_constant_evaluated() FMT_NOEXCEPT {
#ifdef __cpp_lib_is_constant_evaluated
return std::is_constant_evaluated();
#else
return false;
#endif
}

// A helper function to suppress "conditional expression is constant" warnings.
template <typename T> constexpr T const_check(T value) { return value; }

Expand Down Expand Up @@ -481,7 +489,7 @@ inline basic_string_view<Char> to_string_view(
}

template <typename Char>
inline basic_string_view<Char> to_string_view(basic_string_view<Char> s) {
constexpr basic_string_view<Char> to_string_view(basic_string_view<Char> s) {
return s;
}

Expand Down Expand Up @@ -938,9 +946,9 @@ struct arg_data<T, Char, NUM_ARGS, 0> {
T args_[NUM_ARGS != 0 ? NUM_ARGS : +1];

template <typename... U>
FMT_INLINE arg_data(const U&... init) : args_{init...} {}
FMT_INLINE const T* args() const { return args_; }
FMT_INLINE std::nullptr_t named_args() { return nullptr; }
FMT_CONSTEXPR arg_data(const U&... init) : args_{init...} {}
FMT_CONSTEXPR const T* args() const { return args_; }
FMT_CONSTEXPR std::nullptr_t named_args() { return nullptr; }
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
};

template <typename Char>
Expand All @@ -961,7 +969,7 @@ void init_named_args(named_arg_info<Char>* named_args, int arg_count,
}

template <typename... Args>
FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {}
FMT_CONSTEXPR void init_named_args(std::nullptr_t, int, int, const Args&...) {}

template <typename T> struct is_named_arg : std::false_type {};

Expand Down Expand Up @@ -1073,17 +1081,20 @@ template <typename Context> class value {

constexpr FMT_INLINE value(int val = 0) : int_value(val) {}
constexpr FMT_INLINE value(unsigned val) : uint_value(val) {}
FMT_INLINE value(long long val) : long_long_value(val) {}
FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
constexpr FMT_INLINE value(long long val) : long_long_value(val) {}
constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
FMT_INLINE value(int128_t val) : int128_value(val) {}
FMT_INLINE value(uint128_t val) : uint128_value(val) {}
FMT_INLINE value(float val) : float_value(val) {}
FMT_INLINE value(double val) : double_value(val) {}
FMT_INLINE value(long double val) : long_double_value(val) {}
FMT_INLINE value(bool val) : bool_value(val) {}
FMT_INLINE value(char_type val) : char_value(val) {}
FMT_INLINE value(const char_type* val) { string.data = val; }
FMT_INLINE value(basic_string_view<char_type> val) {
constexpr FMT_INLINE value(bool val) : bool_value(val) {}
constexpr FMT_INLINE value(char_type val) : char_value(val) {}
FMT_CONSTEXPR value(const char_type* val) {
string.data = val;
if (is_constant_evaluated()) string.size = {};
}
FMT_CONSTEXPR value(basic_string_view<char_type> val) {
string.data = val.data();
string.size = val.size();
}
Expand Down Expand Up @@ -1406,7 +1417,7 @@ class locale_ref {
const void* locale_; // A type-erased pointer to std::locale.

public:
locale_ref() : locale_(nullptr) {}
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> explicit locale_ref(const Locale& loc);

explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
Expand Down Expand Up @@ -1437,7 +1448,7 @@ template <typename T> int check(unformattable) {
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
return 0;
}
template <typename T, typename U> inline const U& check(const U& val) {
template <typename T, typename U> constexpr const U& check(const U& val) {
return val;
}

Expand All @@ -1446,7 +1457,7 @@ template <typename T, typename U> inline const U& check(const U& val) {
// another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
inline value<Context> make_arg(const T& val) {
constexpr value<Context> make_arg(const T& val) {
return check<T>(arg_mapper<Context>().map(val));
}

Expand Down Expand Up @@ -1480,28 +1491,30 @@ template <typename OutputIt, typename Char> class basic_format_context {
Constructs a ``basic_format_context`` object. References to the arguments are
stored in the object so make sure they have appropriate lifetimes.
*/
basic_format_context(OutputIt out,
basic_format_args<basic_format_context> ctx_args,
detail::locale_ref loc = detail::locale_ref())
constexpr basic_format_context(
OutputIt out, basic_format_args<basic_format_context> ctx_args,
detail::locale_ref loc = detail::locale_ref())
: out_(out), args_(ctx_args), loc_(loc) {}

format_arg arg(int id) const { return args_.get(id); }
format_arg arg(basic_string_view<char_type> name) { return args_.get(name); }
constexpr format_arg arg(int id) const { return args_.get(id); }
FMT_CONSTEXPR format_arg arg(basic_string_view<char_type> name) {
alexezeder marked this conversation as resolved.
Show resolved Hide resolved
return args_.get(name);
}
int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); }
const basic_format_args<basic_format_context>& args() const { return args_; }

detail::error_handler error_handler() { return {}; }
FMT_CONSTEXPR detail::error_handler error_handler() { return {}; }
void on_error(const char* message) { error_handler().on_error(message); }

// Returns an iterator to the beginning of the output range.
iterator out() { return out_; }
FMT_CONSTEXPR iterator out() { return out_; }

// Advances the begin iterator to ``it``.
void advance_to(iterator it) {
if (!detail::is_back_insert_iterator<iterator>()) out_ = it;
}

detail::locale_ref locale() { return loc_; }
FMT_CONSTEXPR detail::locale_ref locale() { return loc_; }
};

template <typename Char>
Expand Down Expand Up @@ -1550,7 +1563,7 @@ class format_arg_store
: 0);

public:
format_arg_store(const Args&... args)
FMT_CONSTEXPR format_arg_store(const Args&... args)
:
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
basic_format_args<Context>(*this),
Expand All @@ -1571,7 +1584,7 @@ class format_arg_store
\endrst
*/
template <typename Context = format_context, typename... Args>
inline format_arg_store<Context, Args...> make_format_args(
constexpr format_arg_store<Context, Args...> make_format_args(
const Args&... args) {
return {args...};
}
Expand Down Expand Up @@ -1644,33 +1657,35 @@ template <typename Context> class basic_format_args {
const format_arg* args_;
};

bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; }
constexpr bool is_packed() const {
return (desc_ & detail::is_unpacked_bit) == 0;
}
bool has_named_args() const {
return (desc_ & detail::has_named_args_bit) != 0;
}

detail::type type(int index) const {
FMT_CONSTEXPR detail::type type(int index) const {
int shift = index * detail::packed_arg_bits;
unsigned int mask = (1 << detail::packed_arg_bits) - 1;
return static_cast<detail::type>((desc_ >> shift) & mask);
}

basic_format_args(unsigned long long desc,
const detail::value<Context>* values)
constexpr basic_format_args(unsigned long long desc,
const detail::value<Context>* values)
: desc_(desc), values_(values) {}
basic_format_args(unsigned long long desc, const format_arg* args)
constexpr basic_format_args(unsigned long long desc, const format_arg* args)
: desc_(desc), args_(args) {}

public:
basic_format_args() : desc_(0) {}
constexpr basic_format_args() : desc_(0), args_(nullptr) {}

/**
\rst
Constructs a `basic_format_args` object from `~fmt::format_arg_store`.
\endrst
*/
template <typename... Args>
FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store)
constexpr basic_format_args(const format_arg_store<Context, Args...>& store)
Comment on lines -1673 to +1688
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and elsewhere: please keep FMT_INLINE.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I missed that FMT_INLINE has an attribute inside, and this line can use FMT_INLINE and constexpr. But those lines where FMT_INLINE was replaced by FMT_CONSTEXPR(20) will have double inline specifier, which makes some compilers unhappy and some very unhappy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So looks like one of these macros shouldn't set inline and that would be really strange if this macro is going to be FMT_INLINE.

I can give an example of Hedley, it does not fallback to inline in the case where constexpr is not available - https://github.com/nemequ/hedley/blob/5e50f6b735aeb4e09e3b2ad3d1717db3c0613bfa/hedley.h#L1269-L1276
and I think it's right because I can always mark a function as inline with or without constexpr, no matter that constexpr implies inline.

In case you agree with me here I can create a separate PR to remove inline from FMT_CONSTEXPR(20) macro. Otherwise I just don't know how to fix these problems without introducing FMT_CONSTEXPR(20)_NO_INLINE macro, which probably would be also bad naming because there is FMT_NOINLINE macro that expands to additional attribute...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you agree with me here I can create a separate PR to remove inline from FMT_CONSTEXPR(20) macro.

Sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done #2075

: basic_format_args(store.desc, store.data_.args()) {}

/**
Expand All @@ -1679,20 +1694,20 @@ template <typename Context> class basic_format_args {
`~fmt::dynamic_format_arg_store`.
\endrst
*/
FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store)
constexpr basic_format_args(const dynamic_format_arg_store<Context>& store)
: basic_format_args(store.get_types(), store.data()) {}

/**
\rst
Constructs a `basic_format_args` object from a dynamic set of arguments.
\endrst
*/
basic_format_args(const format_arg* args, int count)
constexpr basic_format_args(const format_arg* args, int count)
: basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count),
args) {}

/** Returns the argument with the specified id. */
format_arg get(int id) const {
FMT_CONSTEXPR format_arg get(int id) const {
format_arg arg;
if (!is_packed()) {
if (id < max_size()) arg = args_[id];
Expand Down
Loading