
This change makes std::make_format_args refuse to create dangling references to temporaries. This makes the std::vformat API safer. This was approved in Kona 2023 as a DR for C++20 so the change is implemented unconditionally. libstdc++-v3/ChangeLog: * include/bits/chrono_io.h (__formatter_chrono): Always use lvalue arguments to make_format_args. * include/std/format (make_format_args): Change parameter pack from forwarding references to lvalue references. Remove use of remove_reference_t which is now unnecessary. (format_to, formatted_size): Remove incorrect forwarding of arguments. * include/std/ostream (print): Remove forwarding of arguments. * include/std/print (print): Likewise. * testsuite/20_util/duration/io.cc: Use lvalues as arguments to make_format_args. * testsuite/std/format/arguments/args.cc: Likewise. * testsuite/std/format/arguments/lwg3810.cc: Likewise. * testsuite/std/format/functions/format.cc: Likewise. * testsuite/std/format/functions/vformat_to.cc: Likewise. * testsuite/std/format/string.cc: Likewise. * testsuite/std/time/day/io.cc: Likewise. * testsuite/std/time/month/io.cc: Likewise. * testsuite/std/time/weekday/io.cc: Likewise. * testsuite/std/time/year/io.cc: Likewise. * testsuite/std/time/year_month_day/io.cc: Likewise. * testsuite/std/format/arguments/args_neg.cc: New test.
133 lines
3.2 KiB
C++
133 lines
3.2 KiB
C++
// { dg-do run { target c++20 } }
|
|
// { dg-require-namedlocale "fr_FR.ISO8859-15" }
|
|
// { dg-timeout-factor 2 }
|
|
|
|
#include <chrono>
|
|
#include <sstream>
|
|
#include <testsuite_hooks.h>
|
|
|
|
void
|
|
test_ostream()
|
|
{
|
|
using std::ostringstream;
|
|
using namespace std::chrono;
|
|
|
|
ostringstream ss;
|
|
ss << day(1) << ' ' << day(11) << ' ' << day(21) << ' ' << day(31)
|
|
<< ' ' << day(41);
|
|
auto s = ss.str();
|
|
VERIFY( s == "01 11 21 31 41 is not a valid day" );
|
|
|
|
ss.str("");
|
|
ss.imbue(std::locale(ISO_8859(15,fr_FR)));
|
|
ss << day(1);
|
|
VERIFY( ss.str() == "01" );
|
|
}
|
|
|
|
void
|
|
test_format()
|
|
{
|
|
using std::chrono::day;
|
|
|
|
auto s = std::format("{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
|
|
VERIFY( s == "01% 1\t11%11\n" );
|
|
auto ws = std::format(L"{:%d%%%e%t}{:%d%%%e%n}", day(1), day(11));
|
|
VERIFY( ws == L"01% 1\t11%11\n" );
|
|
|
|
VERIFY( std::format("{} {}", day(8), day(0)) == "08 00 is not a valid day" );
|
|
|
|
s = std::format("{:%Od}", day(1));
|
|
VERIFY( s == "01" );
|
|
s = std::format(std::locale::classic(), "{:%Od}", day(1));
|
|
VERIFY( s == "01" );
|
|
s = std::format(std::locale::classic(), "{:L%Od}", day(1));
|
|
VERIFY( s == "01" );
|
|
// TODO test "{:L%Od}" with locale that has alternative numeric rep.
|
|
|
|
std::string_view specs = "aAbBcCdDeFgGhHIjmMpqQrRSTuUVwWxXyYzZ";
|
|
std::string_view my_specs = "de";
|
|
for (char c : specs)
|
|
{
|
|
char fmt[] = { '{', ':', '%', c, '}' };
|
|
try
|
|
{
|
|
day d(1);
|
|
(void) std::vformat(std::string_view(fmt, 5), std::make_format_args(d));
|
|
// The call above should throw for any conversion-spec not in my_specs:
|
|
VERIFY(my_specs.find(c) != my_specs.npos);
|
|
}
|
|
catch (const std::format_error& e)
|
|
{
|
|
VERIFY(my_specs.find(c) == my_specs.npos);
|
|
std::string_view s = e.what();
|
|
// Libstdc++-specific message:
|
|
VERIFY(s.find("format argument does not contain the information "
|
|
"required by the chrono-specs") != s.npos);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
test_parse()
|
|
{
|
|
using namespace std::chrono;
|
|
day d(0);
|
|
minutes offset;
|
|
std::string abbrev;
|
|
std::istringstream is("2023-08-10 12:46 +01 BST<");
|
|
VERIFY( is >> parse("%F %R %z %Z", d, abbrev, offset) );
|
|
VERIFY( ! is.eof() );
|
|
VERIFY( d == 10d );
|
|
VERIFY( abbrev == "BST" );
|
|
VERIFY( offset == 60min );
|
|
|
|
abbrev = "nope";
|
|
offset = 999min;
|
|
is.clear();
|
|
is.str("30");
|
|
VERIFY( is >> parse("%d", d, abbrev, offset) );
|
|
VERIFY( ! is.eof() );
|
|
VERIFY( d == 30d );
|
|
VERIFY( abbrev == "nope" );
|
|
VERIFY( offset == 999min );
|
|
|
|
d = day(255);
|
|
is.clear();
|
|
is.str("2023-02-30");
|
|
is >> parse("%F", d); // Feb 30 is not a valid day
|
|
VERIFY( is.fail() );
|
|
VERIFY( d == day(255) );
|
|
|
|
is.clear();
|
|
is.str("February 30");
|
|
is >> parse("%B %d", d); // Feb 30 is not a valid day
|
|
VERIFY( is.fail() );
|
|
VERIFY( d == day(255) );
|
|
|
|
is.clear();
|
|
is.str("February 29");
|
|
is >> parse("%B %d", d); // But Feb 29 could be valid.
|
|
VERIFY( is.good() );
|
|
VERIFY( d == 29d );
|
|
|
|
d = day(255);
|
|
is.clear();
|
|
is.str("2023 Feb 29");
|
|
is >> parse("%Y %B %d", d); // But 2023 is not a leap year.
|
|
VERIFY( is.fail() );
|
|
VERIFY( d == day(255) );
|
|
|
|
d = day(255);
|
|
is.clear();
|
|
is.str("20 Feb 29");
|
|
is >> parse("%y %B %d", d); // But 2020 is a leap year.
|
|
VERIFY( is.good() );
|
|
VERIFY( d == 29d );
|
|
}
|
|
|
|
int main()
|
|
{
|
|
test_ostream();
|
|
test_format();
|
|
test_parse();
|
|
}
|