@@ -1165,11 +1165,11 @@ ftms = {
// 202305 P2757R3 Type checking format args
// 202306 P2637R3 Member visit
// 202311 P2918R2 Runtime format strings II
- // values = {
- // v = 202305;
- // cxxmin = 26;
- // hosted = yes;
- // };
+ values = {
+ v = 202305;
+ cxxmin = 26;
+ hosted = yes;
+ };
// 201907 Text Formatting, Integration of chrono, printf corner cases.
// 202106 std::format improvements.
// 202110 Fixing locale handling in chrono formatters, generator-like types.
@@ -1304,7 +1304,12 @@
#undef __glibcxx_want_barrier
#if !defined(__cpp_lib_format)
-# if (__cplusplus >= 202002L) && _GLIBCXX_HOSTED
+# if (__cplusplus > 202302L) && _GLIBCXX_HOSTED
+# define __glibcxx_format 202305L
+# if defined(__glibcxx_want_all) || defined(__glibcxx_want_format)
+# define __cpp_lib_format 202305L
+# endif
+# elif (__cplusplus >= 202002L) && _GLIBCXX_HOSTED
# define __glibcxx_format 202304L
# if defined(__glibcxx_want_all) || defined(__glibcxx_want_format)
# define __cpp_lib_format 202304L
@@ -222,6 +222,9 @@ namespace __format
inline void
__failed_to_parse_format_spec()
{ __throw_format_error("format error: failed to parse format-spec"); }
+
+ template<typename _CharT> class _Scanner;
+
} // namespace __format
/// @endcond
@@ -241,9 +244,8 @@ namespace __format
using iterator = const_iterator;
constexpr explicit
- basic_format_parse_context(basic_string_view<_CharT> __fmt,
- size_t __num_args = 0) noexcept
- : _M_begin(__fmt.begin()), _M_end(__fmt.end()), _M_num_args(__num_args)
+ basic_format_parse_context(basic_string_view<_CharT> __fmt) noexcept
+ : _M_begin(__fmt.begin()), _M_end(__fmt.end())
{ }
basic_format_parse_context(const basic_format_parse_context&) = delete;
@@ -283,13 +285,78 @@ namespace __format
__format::__invalid_arg_id_in_format_string();
}
+#if __cpp_lib_format >= 202305L
+ template<typename... _Ts>
+ constexpr void
+ check_dynamic_spec(size_t __id) noexcept;
+
+ constexpr void
+ check_dynamic_spec_integral(size_t __id) noexcept
+ {
+ check_dynamic_spec<int, unsigned, long long, unsigned long long>(__id);
+ }
+
+ constexpr void
+ check_dynamic_spec_string(size_t __id) noexcept
+ {
+ check_dynamic_spec<const char_type*, basic_string_view<_CharT>>(__id);
+ }
+
+ private:
+ // Check the Mandates: condition for check_dynamic_spec<Ts...>(n)
+ template<typename... _Ts>
+ static consteval bool
+ __check_dynamic_spec_types()
+ {
+ if constexpr (sizeof...(_Ts))
+ {
+ int __counts[] = {
+ (is_same_v<bool, _Ts> + ...),
+ (is_same_v<_CharT, _Ts> + ...),
+ (is_same_v<int, _Ts> + ...),
+ (is_same_v<unsigned, _Ts> + ...),
+ (is_same_v<long long, _Ts> + ...),
+ (is_same_v<unsigned long long, _Ts> + ...),
+ (is_same_v<float, _Ts> + ...),
+ (is_same_v<double, _Ts> + ...),
+ (is_same_v<long double, _Ts> + ...),
+ (is_same_v<const _CharT*, _Ts> + ...),
+ (is_same_v<basic_string_view<_CharT>, _Ts> + ...),
+ (is_same_v<const void*, _Ts> + ...)
+ };
+ int __sum = 0;
+ for (int __c : __counts)
+ {
+ __sum += __c;
+ if (__c > 1)
+ __invalid_dynamic_spec("non-unique template argument type");
+ }
+ if (__sum != sizeof...(_Ts))
+ __invalid_dynamic_spec("disallowed template argument type");
+ }
+ return true;
+ }
+
+ // This must not be constexpr.
+ static void __invalid_dynamic_spec(const char*);
+
+ friend __format::_Scanner<_CharT>;
+#endif
+
+ // This constructor should only be used by the implementation.
+ constexpr explicit
+ basic_format_parse_context(basic_string_view<_CharT> __fmt,
+ size_t __num_args) noexcept
+ : _M_begin(__fmt.begin()), _M_end(__fmt.end()), _M_num_args(__num_args)
+ { }
+
private:
iterator _M_begin;
iterator _M_end;
enum _Indexing { _Unknown, _Manual, _Auto };
_Indexing _M_indexing = _Unknown;
size_t _M_next_arg_id = 0;
- size_t _M_num_args;
+ size_t _M_num_args = 0;
};
/// @cond undocumented
@@ -549,6 +616,9 @@ namespace __format
__pc.check_arg_id(__v);
__val = __v;
}
+#if __cpp_lib_format >= 202305L
+ __pc.check_dynamic_spec_integral(__val);
+#endif
++__first; // past the '}'
}
return __first;
@@ -3205,6 +3275,10 @@ namespace __format
template<typename _Context, typename... _Args>
class _Arg_store;
+ template<typename _Ch, typename _Tp>
+ consteval _Arg_t
+ __to_arg_t_enum() noexcept;
+
} // namespace __format
/// @endcond
@@ -3486,6 +3560,10 @@ namespace __format
friend decltype(auto)
visit_format_arg(_Visitor&& __vis, basic_format_arg<_Ctx>);
+ template<typename _Ch, typename _Tp>
+ friend consteval __format::_Arg_t
+ __format::__to_arg_t_enum() noexcept;
+
template<typename _Visitor>
decltype(auto)
_M_visit(_Visitor&& __vis, __format::_Arg_t __type)
@@ -3901,7 +3979,11 @@ namespace __format
{
using iterator = typename basic_format_parse_context<_CharT>::iterator;
- basic_format_parse_context<_CharT> _M_pc;
+ struct _Parse_context : basic_format_parse_context<_CharT>
+ {
+ using basic_format_parse_context<_CharT>::basic_format_parse_context;
+ const _Arg_t* _M_types = nullptr;
+ } _M_pc;
constexpr explicit
_Scanner(basic_string_view<_CharT> __str, size_t __nargs = -1)
@@ -4061,6 +4143,16 @@ namespace __format
}
};
+ template<typename _CharT, typename _Tp>
+ consteval _Arg_t
+ __to_arg_t_enum() noexcept
+ {
+ using _Context = __format::__format_context<_CharT>;
+ using _Fmt_arg = basic_format_arg<_Context>;
+ using _NormalizedTp = typename _Fmt_arg::template _Normalize<_Tp>;
+ return _Fmt_arg::template _S_to_enum<_NormalizedTp>();
+ }
+
// Validate a format string for Args.
template<typename _CharT, typename... _Args>
class _Checking_scanner : public _Scanner<_CharT>
@@ -4070,10 +4162,14 @@ namespace __format
"std::formatter must be specialized for each type being formatted");
public:
- constexpr
+ consteval
_Checking_scanner(basic_string_view<_CharT> __str)
: _Scanner<_CharT>(__str, sizeof...(_Args))
- { }
+ {
+#if __cpp_lib_format >= 202305L
+ this->_M_pc._M_types = _M_types.data();
+#endif
+ }
private:
constexpr void
@@ -4104,6 +4200,11 @@ namespace __format
else
__builtin_unreachable();
}
+
+#if __cpp_lib_format >= 202305L
+ array<_Arg_t, sizeof...(_Args)>
+ _M_types{ { __format::__to_arg_t_enum<_CharT, _Args>()... } };
+#endif
};
template<typename _Out, typename _CharT, typename _Context>
@@ -4202,6 +4303,33 @@ namespace __format
} // namespace __format
/// @endcond
+#if __cpp_lib_format >= 202305L
+ template<typename _CharT>
+ template<typename... _Ts>
+ constexpr void
+ basic_format_parse_context<_CharT>::check_dynamic_spec(size_t __id) noexcept
+ {
+ constexpr bool __ok = __check_dynamic_spec_types<_Ts...>();
+
+ if consteval {
+ if (__id >= _M_num_args)
+ __format::__invalid_arg_id_in_format_string();
+ if constexpr (sizeof...(_Ts) != 0)
+ {
+ using _Parse_context = __format::_Scanner<_CharT>::_Parse_context;
+ auto __arg = static_cast<_Parse_context*>(this)->_M_types[__id];
+ __format::_Arg_t __types[] = {
+ __format::__to_arg_t_enum<_CharT, _Ts>()...
+ };
+ for (auto __t : __types)
+ if (__arg == __t)
+ return;
+ }
+ __invalid_dynamic_spec("arg(id) type does not match");
+ }
+ }
+#endif
+
template<typename _CharT, typename... _Args>
template<typename _Tp>
requires convertible_to<const _Tp&, basic_string_view<_CharT>>
@@ -8,6 +8,8 @@
# error "Feature test macro for std::format is missing in <format>"
#elif __cpp_lib_format < 202110L
# error "Feature test macro for std::format has wrong value in <format>"
+#elif __cplusplus > 202302L && __cpp_lib_format < 202305L
+# error "Feature test macro for std::format has wrong value in <format>"
#endif
#ifndef __cpp_lib_format_uchar
@@ -22,6 +24,8 @@
# error "Feature test macro for std::format is missing in <version>"
#elif __cpp_lib_format < 202110L
# error "Feature test macro for std::format has wrong value in <version>"
+#elif __cplusplus > 202302L && __cpp_lib_format < 202305L
+# error "Feature test macro for std::format has wrong value in <version>"
#endif
#ifndef __cpp_lib_format_uchar
@@ -3,6 +3,91 @@
#include <format>
#include <testsuite_hooks.h>
+static_assert(std::is_constructible_v<std::format_parse_context,
+ std::string_view>);
+static_assert(std::is_constructible_v<std::wformat_parse_context,
+ std::wstring_view>);
+
+#if __cpp_lib_format < 202305
+constexpr bool construct_with_num_args = true;
+#else
+constexpr bool construct_with_num_args = false;
+#endif
+
+static_assert(std::is_constructible_v<std::format_parse_context,
+ std::string_view, std::size_t>
+ == construct_with_num_args);
+static_assert(std::is_constructible_v<std::wformat_parse_context,
+ std::wstring_view, std::size_t>
+ == construct_with_num_args);
+
+static_assert( ! std::is_constructible_v<std::format_parse_context,
+ std::wstring_view>);
+static_assert( ! std::is_constructible_v<std::wformat_parse_context,
+ std::string_view>);
+
+static_assert( ! std::is_convertible_v<std::string_view,
+ std::format_parse_context> );
+static_assert( ! std::is_convertible_v<std::wstring_view,
+ std::wformat_parse_context> );
+
+static_assert( ! std::is_default_constructible_v<std::format_parse_context> );
+static_assert( ! std::is_copy_constructible_v<std::format_parse_context> );
+static_assert( ! std::is_move_constructible_v<std::format_parse_context> );
+static_assert( ! std::is_copy_assignable_v<std::format_parse_context> );
+static_assert( ! std::is_move_assignable_v<std::format_parse_context> );
+
+// This concept is satisfied if the next_arg_id() call is a constant expression
+template<typename Ch, typename PC = std::basic_format_parse_context<Ch>>
+concept arg_id_available = requires {
+ typename std::integral_constant<std::size_t,
+ PC({}).next_arg_id()>::type;
+};
+
+void
+test_members()
+{
+ std::string_view s = "spec string";
+
+ std::format_parse_context pc(s);
+
+ VERIFY( pc.begin() == s.begin() );
+ VERIFY( pc.end() == s.end() );
+ pc.advance_to(s.begin() + 5);
+ VERIFY( pc.begin() == s.begin() + 5 );
+
+ // Runtime calls to these do not check for the correct number of args.
+ VERIFY( pc.next_arg_id() == 0 );
+ VERIFY( pc.next_arg_id() == 1 );
+ VERIFY( pc.next_arg_id() == 2 );
+ try
+ {
+ // Cannot mix manual and automatic indexing.
+ pc.check_arg_id(0);
+ VERIFY( false );
+ }
+ catch (const std::format_error&)
+ {
+ }
+ // But they do check during constant evaluation:
+ VERIFY( ! arg_id_available<char> );
+ VERIFY( ! arg_id_available<wchar_t> );
+
+ std::format_parse_context pc2("");
+ pc2.check_arg_id(2);
+ pc2.check_arg_id(1);
+ pc2.check_arg_id(3);
+ try
+ {
+ // Cannot mix manual and automatic indexing.
+ (void) pc2.next_arg_id();
+ VERIFY( false );
+ }
+ catch (const std::format_error&)
+ {
+ }
+}
+
template<typename T, bool auto_indexing = true>
bool
is_std_format_spec_for(std::string_view spec)
@@ -357,6 +442,65 @@ test_custom()
VERIFY( ! is_std_format_spec_for<S>("") );
}
+#if __cpp_lib_format >= 202305
+struct X { };
+
+template<>
+struct std::formatter<X, char>
+{
+ constexpr std::format_parse_context::iterator
+ parse(std::format_parse_context& pc)
+ {
+ std::string_view spec(pc.begin(), pc.end());
+ auto p = spec.find('}');
+ if (p != std::string_view::npos)
+ spec = spec.substr(0, p); // truncate to closing brace
+ if (spec == "int")
+ {
+ pc.check_dynamic_spec_integral(pc.next_arg_id());
+ integer = true;
+ }
+ else if (spec == "str")
+ {
+ pc.check_dynamic_spec_string(pc.next_arg_id());
+ integer = false;
+ }
+ else
+ throw std::format_error("invalid format-spec");
+ return pc.begin() + spec.size();
+ }
+
+ std::format_context::iterator
+ format(X, std::format_context& c) const
+ {
+ std::visit_format_arg([this]<typename T>(T) {
+ if (is_integral_v<T> != this->integer)
+ throw std::format_error("invalid argument type");
+ }, c.arg(1));
+ return c.out();
+ }
+private:
+ bool integer = false;
+};
+#endif
+
+void
+test_dynamic_type_check()
+{
+#if __cpp_lib_format >= 202305
+ std::format_parse_context pc("{1}.{2}");
+
+ // None of these calls should do anything at runtime, only during consteval:
+ pc.check_dynamic_spec<>(0);
+ pc.check_dynamic_spec<int, const char*>(0);
+ pc.check_dynamic_spec_integral(0);
+ pc.check_dynamic_spec_string(0);
+
+ (void) std::format("{:int}", X{}, 42L);
+ (void) std::format("{:str}", X{}, "H2G2");
+#endif
+}
+
int main()
{
test_char();
@@ -366,4 +510,5 @@ int main()
test_string();
test_pointer();
test_custom();
+ test_dynamic_type_check();
}
new file mode 100644
@@ -0,0 +1,39 @@
+// { dg-do compile { target c++26 } }
+
+#include <format>
+
+void
+test_invalid()
+{
+ std::format_parse_context pc("");
+
+ // These types are all valid:
+ pc.check_dynamic_spec<bool, char, int, unsigned, long long,
+ unsigned long long, float, double, long double,
+ const char*, std::string_view, const void*>(0);
+ // For some reason, an empty pack of types is valid:
+ pc.check_dynamic_spec<>(0);
+
+ pc.check_dynamic_spec<void>(0); // { dg-error "here" }
+ // const void* is allowed, but void* is not
+ pc.check_dynamic_spec<void*>(0); // { dg-error "here" }
+ // int and long long are allowed, but long is not
+ pc.check_dynamic_spec<long>(0); // { dg-error "here" }
+ // char_type is allowed, but other character types are not
+ pc.check_dynamic_spec<wchar_t>(0); // { dg-error "here" }
+ pc.check_dynamic_spec<char8_t>(0); // { dg-error "here" }
+ // std::string_view is allowed, but std::string is not
+ pc.check_dynamic_spec<std::string>(0); // { dg-error "here" }
+ pc.check_dynamic_spec<int, bool, int>(0); // { dg-error "here" }
+
+ std::wformat_parse_context wpc(L"");
+ wpc.check_dynamic_spec<bool, wchar_t, int, unsigned, long long,
+ unsigned long long, float, double, long double,
+ const wchar_t*, std::wstring_view, const void*>(0);
+ wpc.check_dynamic_spec<char>(0); // { dg-error "here" }
+ wpc.check_dynamic_spec<char16_t>(0); // { dg-error "here" }
+ wpc.check_dynamic_spec<char32_t>(0); // { dg-error "here" }
+}
+
+// Each failure above will point to a call to this non-constexpr function:
+// { dg-error "__invalid_dynamic_spec" "" { target *-*-* } 0 }
@@ -109,9 +109,16 @@ test_format_spec()
VERIFY( ! is_format_string_for("{:#?}", "str") );
VERIFY( ! is_format_string_for("{:#?}", 'c') );
+ // The 0 option is not valid for charT and bool.
VERIFY( ! is_format_string_for("{:0c}", 'c') );
VERIFY( ! is_format_string_for("{:0s}", true) );
+ // Dynamic width arg must be an integer type.
+ VERIFY( ! is_format_string_for("{:{}d}", 1, 1.5) );
+ VERIFY( ! is_format_string_for("{:{}d}", 1, true) );
+ VERIFY( ! is_format_string_for("{:{}d}", 1, "str") );
+ VERIFY( ! is_format_string_for("{:{}d}", 1, nullptr) );
+
// Precision only valid for string and floating-point types.
VERIFY( ! is_format_string_for("{:.3d}", 1) );
VERIFY( ! is_format_string_for("{:3.3d}", 1) );
@@ -119,6 +126,12 @@ test_format_spec()
VERIFY( ! is_format_string_for("{:3.3s}", 'c') );
VERIFY( ! is_format_string_for("{:3.3p}", nullptr) );
+ // Dynamic precision arg must be an integer type.
+ VERIFY( ! is_format_string_for("{:.{}f}", 1.0, 1.5) );
+ VERIFY( ! is_format_string_for("{:.{}f}", 1.0, true) );
+ VERIFY( ! is_format_string_for("{:.{}f}", 1.0, "str") );
+ VERIFY( ! is_format_string_for("{:.{}f}", 1.0, nullptr) );
+
// Invalid presentation types for integers.
VERIFY( ! is_format_string_for("{:f}", 1) );
VERIFY( ! is_format_string_for("{:s}", 1) );