blob: 6294dcf43f5d7861323bdbd4ad0df3ad5acfc822 [file] [log] [blame]
// { dg-do run { target c++20 } }
#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)
{
std::format_parse_context pc(spec);
if (auto_indexing)
(void) pc.next_arg_id();
else
pc.check_arg_id(0);
std::formatter<T> f;
try {
auto end = f.parse(pc);
VERIFY( end == spec.end() || *end == '}' );
return true;
} catch (const std::format_error&) {
return false;
}
}
#if __cpp_lib_format_ranges
constexpr bool escaped_strings_supported = true;
#else
constexpr bool escaped_strings_supported = false;
#endif
void
test_char()
{
VERIFY( is_std_format_spec_for<char>("") );
VERIFY( is_std_format_spec_for<char>("<") );
VERIFY( is_std_format_spec_for<char>(">") );
VERIFY( is_std_format_spec_for<char>("^") );
VERIFY( is_std_format_spec_for<char>("0<") );
VERIFY( is_std_format_spec_for<char>("0>") );
VERIFY( is_std_format_spec_for<char>("0^") );
VERIFY( ! is_std_format_spec_for<char>("{^") );
VERIFY( ! is_std_format_spec_for<char>("+") );
VERIFY( ! is_std_format_spec_for<char>("-") );
VERIFY( ! is_std_format_spec_for<char>(" ") );
VERIFY( ! is_std_format_spec_for<char>("#") );
VERIFY( is_std_format_spec_for<char>("0d") );
VERIFY( ! is_std_format_spec_for<char>("0") );
VERIFY( ! is_std_format_spec_for<char>("00d") );
VERIFY( is_std_format_spec_for<char>("01d") );
VERIFY( is_std_format_spec_for<char>("0{}d") );
VERIFY( ! is_std_format_spec_for<char>("0{1}d") );
VERIFY(( is_std_format_spec_for<char, false>("0{1}d") ));
VERIFY( is_std_format_spec_for<char>("1") );
VERIFY( ! is_std_format_spec_for<char>("-1") );
VERIFY( is_std_format_spec_for<char>("-1d") ); // sign and width
VERIFY( ! is_std_format_spec_for<char>(".") );
VERIFY( ! is_std_format_spec_for<char>(".1") );
VERIFY( is_std_format_spec_for<char>("c") );
VERIFY( is_std_format_spec_for<char>("b") );
VERIFY( is_std_format_spec_for<char>("B") );
VERIFY( is_std_format_spec_for<char>("d") );
VERIFY( is_std_format_spec_for<char>("o") );
VERIFY( is_std_format_spec_for<char>("x") );
VERIFY( is_std_format_spec_for<char>("X") );
VERIFY( ! is_std_format_spec_for<char>("s") );
VERIFY( is_std_format_spec_for<char>("?") == escaped_strings_supported );
VERIFY( ! is_std_format_spec_for<char>("a") );
VERIFY( ! is_std_format_spec_for<char>("A") );
VERIFY( ! is_std_format_spec_for<char>("f") );
VERIFY( ! is_std_format_spec_for<char>("F") );
VERIFY( ! is_std_format_spec_for<char>("g") );
VERIFY( ! is_std_format_spec_for<char>("G") );
VERIFY( ! is_std_format_spec_for<char>("+c") );
VERIFY( ! is_std_format_spec_for<char>("+?") );
VERIFY( is_std_format_spec_for<char>("+d") );
}
void
test_int()
{
VERIFY( is_std_format_spec_for<int>("") );
VERIFY( is_std_format_spec_for<int>("<") );
VERIFY( is_std_format_spec_for<int>(">") );
VERIFY( is_std_format_spec_for<int>("^") );
VERIFY( is_std_format_spec_for<int>("0<") );
VERIFY( is_std_format_spec_for<int>("0>") );
VERIFY( is_std_format_spec_for<int>("0^") );
VERIFY( ! is_std_format_spec_for<int>("{^") );
VERIFY( is_std_format_spec_for<int>("+") );
VERIFY( is_std_format_spec_for<int>("-") );
VERIFY( is_std_format_spec_for<int>(" ") );
VERIFY( is_std_format_spec_for<int>("#") );
VERIFY( is_std_format_spec_for<int>("0d") );
VERIFY( is_std_format_spec_for<int>("0") );
VERIFY( ! is_std_format_spec_for<int>("00d") );
VERIFY( is_std_format_spec_for<int>("01d") );
VERIFY( ! is_std_format_spec_for<int>("0{1}d") );
VERIFY(( is_std_format_spec_for<int>("0{}d") ));
VERIFY(( ! is_std_format_spec_for<int>("0{1}d") ));
VERIFY(( is_std_format_spec_for<int, false>("0{1}d") ));
VERIFY( is_std_format_spec_for<int>("1") );
VERIFY( is_std_format_spec_for<int>("-1") ); // sign and width
VERIFY( ! is_std_format_spec_for<int>(".") );
VERIFY( ! is_std_format_spec_for<int>(".1") );
VERIFY( is_std_format_spec_for<int>("c") );
VERIFY( is_std_format_spec_for<int>("b") );
VERIFY( is_std_format_spec_for<int>("B") );
VERIFY( is_std_format_spec_for<int>("d") );
VERIFY( is_std_format_spec_for<int>("o") );
VERIFY( is_std_format_spec_for<int>("x") );
VERIFY( is_std_format_spec_for<int>("X") );
VERIFY( ! is_std_format_spec_for<int>("s") );
VERIFY( ! is_std_format_spec_for<int>("?") );
VERIFY( ! is_std_format_spec_for<int>("a") );
VERIFY( ! is_std_format_spec_for<int>("A") );
VERIFY( ! is_std_format_spec_for<int>("f") );
VERIFY( ! is_std_format_spec_for<int>("F") );
VERIFY( ! is_std_format_spec_for<int>("g") );
VERIFY( ! is_std_format_spec_for<int>("G") );
VERIFY( ! is_std_format_spec_for<int>("p") );
VERIFY( ! is_std_format_spec_for<int>("P") );
VERIFY( is_std_format_spec_for<int>("+c") ); // But LWG 3644 would change it.
VERIFY( ! is_std_format_spec_for<int>("+?") );
VERIFY( is_std_format_spec_for<int>("+d") );
}
void
test_bool()
{
VERIFY( is_std_format_spec_for<bool>("") );
VERIFY( is_std_format_spec_for<bool>("<") );
VERIFY( is_std_format_spec_for<bool>(">") );
VERIFY( is_std_format_spec_for<bool>("^") );
VERIFY( is_std_format_spec_for<bool>("0<") );
VERIFY( is_std_format_spec_for<bool>("0>") );
VERIFY( is_std_format_spec_for<bool>("0^") );
VERIFY( ! is_std_format_spec_for<bool>("{^") );
VERIFY( ! is_std_format_spec_for<bool>("+") );
VERIFY( ! is_std_format_spec_for<bool>("-") );
VERIFY( ! is_std_format_spec_for<bool>(" ") );
VERIFY( ! is_std_format_spec_for<bool>("#") );
VERIFY( is_std_format_spec_for<bool>("0d") );
VERIFY( ! is_std_format_spec_for<bool>("0") );
VERIFY( ! is_std_format_spec_for<bool>("00d") );
VERIFY( is_std_format_spec_for<bool>("01d") );
VERIFY( is_std_format_spec_for<bool>("1") );
VERIFY( ! is_std_format_spec_for<bool>("-1") );
VERIFY( is_std_format_spec_for<bool>("-1d") ); // sign and width
VERIFY( ! is_std_format_spec_for<bool>(".") );
VERIFY( ! is_std_format_spec_for<bool>(".1") );
VERIFY( ! is_std_format_spec_for<bool>("c") );
VERIFY( is_std_format_spec_for<bool>("b") );
VERIFY( is_std_format_spec_for<bool>("B") );
VERIFY( is_std_format_spec_for<bool>("d") );
VERIFY( is_std_format_spec_for<bool>("o") );
VERIFY( is_std_format_spec_for<bool>("x") );
VERIFY( is_std_format_spec_for<bool>("X") );
VERIFY( is_std_format_spec_for<bool>("s") );
VERIFY( ! is_std_format_spec_for<bool>("?") );
VERIFY( ! is_std_format_spec_for<bool>("a") );
VERIFY( ! is_std_format_spec_for<bool>("A") );
VERIFY( ! is_std_format_spec_for<bool>("f") );
VERIFY( ! is_std_format_spec_for<bool>("F") );
VERIFY( ! is_std_format_spec_for<bool>("g") );
VERIFY( ! is_std_format_spec_for<bool>("G") );
VERIFY( ! is_std_format_spec_for<bool>("p") );
VERIFY( ! is_std_format_spec_for<bool>("P") );
VERIFY( ! is_std_format_spec_for<bool>("+s") );
VERIFY( is_std_format_spec_for<bool>("+d") );
}
void
test_float()
{
VERIFY( is_std_format_spec_for<float>("") );
VERIFY( is_std_format_spec_for<float>("<") );
VERIFY( is_std_format_spec_for<float>(">") );
VERIFY( is_std_format_spec_for<float>("^") );
VERIFY( is_std_format_spec_for<float>("0<") );
VERIFY( is_std_format_spec_for<float>("0>") );
VERIFY( is_std_format_spec_for<float>("0^") );
VERIFY( ! is_std_format_spec_for<float>("{^") );
VERIFY( is_std_format_spec_for<float>("+") );
VERIFY( is_std_format_spec_for<float>("-") );
VERIFY( is_std_format_spec_for<float>(" ") );
VERIFY( is_std_format_spec_for<float>("#") );
VERIFY( is_std_format_spec_for<float>("0f") );
VERIFY( is_std_format_spec_for<float>("0") );
VERIFY( ! is_std_format_spec_for<float>("00f") );
VERIFY( is_std_format_spec_for<float>("01f") );
VERIFY( is_std_format_spec_for<float>("0{}f") );
VERIFY( ! is_std_format_spec_for<float>("0{1}f") );
VERIFY( ! is_std_format_spec_for<float>("0{1}f") );
VERIFY(( is_std_format_spec_for<float, false>("0{1}f") ));
VERIFY( is_std_format_spec_for<float>("1") );
VERIFY( is_std_format_spec_for<float>("-1") ); // sign and width
VERIFY( ! is_std_format_spec_for<float>(".") );
VERIFY( is_std_format_spec_for<float>(".1") );
VERIFY( is_std_format_spec_for<float>(".{}") );
VERIFY( ! is_std_format_spec_for<float>(".{1}") );
VERIFY(( is_std_format_spec_for<float, false>(".{1}") ));
VERIFY( is_std_format_spec_for<float>("{}.{}") );
VERIFY(( is_std_format_spec_for<float, false>("{1}.{1}") ));
VERIFY(( is_std_format_spec_for<float, false>("{2}.{1}") ));
VERIFY( ! is_std_format_spec_for<float>("c") );
VERIFY( ! is_std_format_spec_for<float>("b") );
VERIFY( ! is_std_format_spec_for<float>("B") );
VERIFY( ! is_std_format_spec_for<float>("d") );
VERIFY( ! is_std_format_spec_for<float>("o") );
VERIFY( ! is_std_format_spec_for<float>("x") );
VERIFY( ! is_std_format_spec_for<float>("X") );
VERIFY( ! is_std_format_spec_for<float>("s") );
VERIFY( ! is_std_format_spec_for<float>("?") );
VERIFY( is_std_format_spec_for<float>("a") );
VERIFY( is_std_format_spec_for<float>("A") );
VERIFY( is_std_format_spec_for<float>("f") );
VERIFY( is_std_format_spec_for<float>("F") );
VERIFY( is_std_format_spec_for<float>("g") );
VERIFY( is_std_format_spec_for<float>("G") );
VERIFY( ! is_std_format_spec_for<float>("p") );
VERIFY( ! is_std_format_spec_for<float>("P") );
VERIFY( is_std_format_spec_for<float>("+f") );
VERIFY( is_std_format_spec_for<float>("_<+#09.6Lf") );
VERIFY( is_std_format_spec_for<float>("<+#09.6Lf") );
VERIFY( is_std_format_spec_for<float>("<+#9.6Lf") );
VERIFY( is_std_format_spec_for<float>(".0006f") );
}
void
test_pointer()
{
VERIFY( is_std_format_spec_for<void*>("") );
VERIFY( is_std_format_spec_for<void*>("<") );
VERIFY( is_std_format_spec_for<void*>(">") );
VERIFY( is_std_format_spec_for<void*>("^") );
VERIFY( is_std_format_spec_for<void*>("0<") );
VERIFY( is_std_format_spec_for<void*>("0>") );
VERIFY( is_std_format_spec_for<void*>("0^") );
VERIFY( ! is_std_format_spec_for<void*>("{^") );
VERIFY( ! is_std_format_spec_for<void*>("+") );
VERIFY( ! is_std_format_spec_for<void*>("-") );
VERIFY( ! is_std_format_spec_for<void*>(" ") );
VERIFY( ! is_std_format_spec_for<void*>("#") );
VERIFY( is_std_format_spec_for<void*>("1") );
VERIFY( ! is_std_format_spec_for<void*>("-1") );
VERIFY( ! is_std_format_spec_for<void*>("-1p") );
VERIFY( ! is_std_format_spec_for<void*>(".") );
VERIFY( ! is_std_format_spec_for<void*>(".1") );
VERIFY( ! is_std_format_spec_for<void*>("c") );
VERIFY( ! is_std_format_spec_for<void*>("b") );
VERIFY( ! is_std_format_spec_for<void*>("B") );
VERIFY( ! is_std_format_spec_for<void*>("d") );
VERIFY( ! is_std_format_spec_for<void*>("o") );
VERIFY( ! is_std_format_spec_for<void*>("x") );
VERIFY( ! is_std_format_spec_for<void*>("X") );
VERIFY( ! is_std_format_spec_for<void*>("s") );
VERIFY( ! is_std_format_spec_for<void*>("?") );
VERIFY( is_std_format_spec_for<void*>("p") );
VERIFY( ! is_std_format_spec_for<void*>("a") );
VERIFY( ! is_std_format_spec_for<void*>("A") );
VERIFY( ! is_std_format_spec_for<void*>("f") );
VERIFY( ! is_std_format_spec_for<void*>("F") );
VERIFY( ! is_std_format_spec_for<void*>("g") );
VERIFY( ! is_std_format_spec_for<void*>("G") );
VERIFY( ! is_std_format_spec_for<void*>("+p") );
#if __cpp_lib_format >= 202304L
// P2510R3 Formatting pointers
VERIFY( is_std_format_spec_for<void*>("P") );
VERIFY( is_std_format_spec_for<void*>("0p") );
VERIFY( is_std_format_spec_for<void*>("0P") );
VERIFY( is_std_format_spec_for<void*>("0") );
VERIFY( is_std_format_spec_for<void*>("01p") );
VERIFY( ! is_std_format_spec_for<void*>("00p") );
#endif
}
void
test_string()
{
VERIFY( is_std_format_spec_for<const char*>("") );
VERIFY( is_std_format_spec_for<const char*>("<") );
VERIFY( is_std_format_spec_for<const char*>(">") );
VERIFY( is_std_format_spec_for<const char*>("^") );
VERIFY( is_std_format_spec_for<const char*>("0<") );
VERIFY( is_std_format_spec_for<const char*>("0>") );
VERIFY( is_std_format_spec_for<const char*>("0^") );
VERIFY( ! is_std_format_spec_for<const char*>("{^") );
VERIFY( ! is_std_format_spec_for<const char*>("+") );
VERIFY( ! is_std_format_spec_for<const char*>("-") );
VERIFY( ! is_std_format_spec_for<const char*>(" ") );
VERIFY( ! is_std_format_spec_for<const char*>("#") );
VERIFY( ! is_std_format_spec_for<const char*>("0") );
VERIFY( ! is_std_format_spec_for<const char*>("01s") );
VERIFY( is_std_format_spec_for<const char*>("1") );
VERIFY( ! is_std_format_spec_for<const char*>("-1") );
VERIFY( ! is_std_format_spec_for<const char*>("-1s") );
VERIFY( ! is_std_format_spec_for<const char*>(".") );
VERIFY( is_std_format_spec_for<const char*>(".1") );
VERIFY( is_std_format_spec_for<const char*>(".{}") );
VERIFY(( is_std_format_spec_for<const char*, false>(".{0}") ));
VERIFY(( is_std_format_spec_for<const char*, false>(".{1}") ));
VERIFY( ! is_std_format_spec_for<const char*>("c") );
VERIFY( ! is_std_format_spec_for<const char*>("b") );
VERIFY( ! is_std_format_spec_for<const char*>("B") );
VERIFY( ! is_std_format_spec_for<const char*>("d") );
VERIFY( ! is_std_format_spec_for<const char*>("o") );
VERIFY( ! is_std_format_spec_for<const char*>("x") );
VERIFY( ! is_std_format_spec_for<const char*>("X") );
VERIFY( is_std_format_spec_for<const char*>("s") );
VERIFY( is_std_format_spec_for<const char*>("?") == escaped_strings_supported );
VERIFY( ! is_std_format_spec_for<const char*>("p") );
VERIFY( ! is_std_format_spec_for<const char*>("P") );
VERIFY( ! is_std_format_spec_for<const char*>("a") );
VERIFY( ! is_std_format_spec_for<const char*>("A") );
VERIFY( ! is_std_format_spec_for<const char*>("f") );
VERIFY( ! is_std_format_spec_for<const char*>("F") );
VERIFY( ! is_std_format_spec_for<const char*>("g") );
VERIFY( ! is_std_format_spec_for<const char*>("G") );
VERIFY( is_std_format_spec_for<const char*>("*^6s") );
VERIFY( is_std_format_spec_for<const char*>(">6s") );
VERIFY( is_std_format_spec_for<const char*>("_<6.4?") == escaped_strings_supported );
}
struct S { };
template<>
struct std::formatter<S, 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)
p = spec.size();
if (p == 0)
throw std::format_error("empty format-spec");
if (spec != "custom")
throw std::format_error("invalid format-spec");
return pc.begin() + p;
}
std::format_context::iterator
format(const S&, std::format_context&) const;
};
void
test_custom()
{
VERIFY( is_std_format_spec_for<S>("custom") );
VERIFY( ! is_std_format_spec_for<S>("customer") );
VERIFY( ! is_std_format_spec_for<S>("custard") );
VERIFY( ! is_std_format_spec_for<S>("") );
}
#if __cpp_lib_format >= 202305
#include <stdfloat>
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());
type = Type::integral;
}
else if (spec == "str")
{
pc.check_dynamic_spec_string(pc.next_arg_id());
type = Type::string;
}
else if (spec == "float")
{
pc.check_dynamic_spec<float, double, long double>(pc.next_arg_id());
type = Type::floating;
}
else if (spec == "other")
type = Type::other;
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) { // { dg-warning "deprecated" "" { target c++26 } }
constexpr bool is_handle
= std::is_same_v<std::basic_format_arg<std::format_context>::handle, T>;
constexpr bool is_integral
= std::is_same_v<int, T> || std::is_same_v<unsigned int, T>
|| is_same_v<long long, T> || std::is_same_v<unsigned long long, T>;
constexpr bool is_string
= std::is_same_v<const char*, T> || std::is_same_v<std::string_view, T>;
constexpr bool is_floating
= std::is_same_v<float, T> || std::is_same_v<double, T>
|| std::is_same_v<long double, T>;
switch (this->type)
{
case Type::other:
if (is_handle) return;
break;
case Type::integral:
if (is_integral) return;
break;
case Type::string:
if (is_string) return;
break;
case Type::floating:
if (is_floating) return;
break;
}
throw std::format_error("invalid argument type");
}, c.arg(1));
return c.out();
}
private:
enum class Type
{
other,
integral,
string,
floating,
};
Type type = Type::other;
};
#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<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");
(void) std::format("{:float}", X{}, 10.0);
#ifdef __STDCPP_BFLOAT16_T__
if constexpr (std::formattable<std::bfloat16_t, char>)
(void) std::format("{:other}", X{}, 10.0bf16);
#endif
#ifdef __STDCPP_FLOAT16_T__
if constexpr (std::formattable<std::float16_t, char>)
(void) std::format("{:other}", X{}, 10.0f16);
#endif
#ifdef __STDCPP_FLOAT32_T__
if constexpr (std::formattable<std::float32_t, char>)
(void) std::format("{:other}", X{}, 10.0f32);
#endif
#ifdef __STDCPP_FLOAT64_T__
if constexpr (std::formattable<std::float64_t, char>)
(void) std::format("{:other}", X{}, 10.0f64);
#endif
#ifdef __STDCPP_FLOAT128_T__
if constexpr (std::formattable<std::float128_t, char>)
(void) std::format("{:other}", X{}, 10.0f128);
#endif
#endif
}
int main()
{
test_char();
test_int();
test_bool();
test_float();
test_string();
test_pointer();
test_custom();
test_dynamic_type_check();
}