| // { dg-do run { target c++20 } } |
| |
| #include <chrono> |
| #include <sstream> |
| #include <testsuite_hooks.h> |
| #include <testsuite_allocator.h> |
| |
| template<typename T, typename CharT> |
| concept stream_extractable |
| = requires (std::basic_istream<CharT>& is) { is >> std::declval<T>(); }; |
| |
| void |
| test_recommended_practice() |
| { |
| std::chrono::seconds s; |
| using parse_manip = decltype(std::chrono::parse("", s)); |
| static_assert( stream_extractable<parse_manip, char> ); |
| static_assert( not stream_extractable<parse_manip, wchar_t> ); |
| using wparse_manip = decltype(std::chrono::parse(L"", s)); |
| static_assert( stream_extractable<wparse_manip, wchar_t> ); |
| static_assert( not stream_extractable<wparse_manip, char> ); |
| |
| // These properties are recommended by the standard, to avoid using a |
| // parse manipulator that has a dangling reference to a format string. |
| static_assert( not std::is_move_constructible_v<parse_manip> ); |
| static_assert( not std::is_move_assignable_v<parse_manip> ); |
| static_assert( not stream_extractable<parse_manip&, char> ); |
| static_assert( not stream_extractable<const parse_manip&, char> ); |
| static_assert( not stream_extractable<wparse_manip&, wchar_t> ); |
| static_assert( not stream_extractable<const wparse_manip&, wchar_t> ); |
| } |
| |
| template<typename... Args> |
| concept parsable = requires(Args... args) { std::chrono::parse(args...); }; |
| |
| const std::string f = "format string"; |
| |
| namespace N |
| { |
| struct A { }; |
| |
| void |
| from_stream(std::istream&, const char* fmt, A&) |
| { |
| VERIFY( fmt == f.c_str() ); |
| } |
| |
| template<typename... Args> |
| void |
| from_stream(std::istream&, const char*, A&, void*, void*) = delete; |
| |
| struct B { }; |
| |
| void |
| from_stream(std::istream&, const char* fmt, B&, std::string* abbrev) |
| { |
| VERIFY( fmt == f.c_str() ); |
| VERIFY( abbrev != nullptr ); |
| } |
| |
| void |
| from_stream(std::istream&, const char*, B&, std::string*, void*) = delete; |
| |
| struct C { }; |
| |
| void |
| from_stream(std::istream&, const char* fmt, C&, std::string* abbrev, |
| std::chrono::minutes* offset) |
| { |
| VERIFY( fmt == f.c_str() ); |
| VERIFY( abbrev == nullptr ); |
| VERIFY( offset != nullptr ); |
| } |
| |
| struct D { }; |
| |
| void |
| from_stream(std::istream&, const char* fmt, D&, std::string* abbrev, |
| std::chrono::minutes* offset) |
| { |
| VERIFY( fmt == f.c_str() ); |
| VERIFY( abbrev != nullptr ); |
| VERIFY( offset != nullptr ); |
| } |
| |
| struct E { }; |
| |
| void |
| from_stream(std::wistream&, const wchar_t*, E&, std::wstring* = nullptr, |
| std::chrono::minutes* = nullptr) |
| { } |
| } |
| |
| void |
| test_adl() |
| { |
| using std::string; |
| using std::wstring; |
| using std::chrono::minutes; |
| |
| string abbrev; |
| minutes offset; |
| |
| // Check that valid calls are well-formed. |
| N::A a; |
| (void) std::chrono::parse(f, a); |
| N::B b; |
| (void) std::chrono::parse(f, b, abbrev); |
| N::C c; |
| (void) std::chrono::parse(f, c, offset); |
| // This satisfies the concept, but would fail the VERIFY assertion: |
| static_assert( parsable<const char*, N::C, string, minutes> ); |
| N::D d; |
| (void) std::chrono::parse(f, d, abbrev, offset); |
| // This satisfies the concept, but would fail the VERIFY assertion: |
| static_assert( parsable<const char*, N::D, minutes> ); |
| |
| // Wide strings. |
| static_assert( parsable<const wchar_t*, N::E, wstring> ); |
| static_assert( parsable<const wchar_t*, N::E, wstring> ); |
| static_assert( parsable<const wchar_t*, N::E, minutes> ); |
| static_assert( parsable<const wchar_t*, N::E, wstring, minutes> ); |
| |
| // Check that invalid calls are properly constrained. |
| |
| // from_stream is only overloaded for N::A without abbrev or offset. |
| static_assert( not parsable<const char*, N::A, std::string> ); |
| static_assert( not parsable<const char*, N::A, minutes> ); |
| static_assert( not parsable<const char*, N::A, string, minutes> ); |
| // from_stream is only overloaded for N::B with abbrev. |
| static_assert( not parsable<const char*, N::B> ); |
| static_assert( not parsable<const char*, N::B, minutes> ); |
| static_assert( not parsable<const char*, N::B, string, minutes> ); |
| // from_stream is only overloaded for N::C with abbrev and minutes. |
| static_assert( not parsable<const char*, N::C> ); |
| static_assert( not parsable<const char*, N::C, string> ); |
| // from_stream is only overloaded for N::D with abbrev and minutes. |
| static_assert( not parsable<const char*, N::D> ); |
| static_assert( not parsable<const char*, N::D, string> ); |
| |
| // Mismatched strings |
| static_assert( not parsable<string, std::chrono::year, wstring> ); |
| static_assert( not parsable<string, std::chrono::year, wstring, minutes> ); |
| |
| using Alloc = __gnu_test::SimpleAllocator<char>; |
| using String = std::basic_string<char, std::char_traits<char>, Alloc>; |
| // Custom allocator |
| static_assert( parsable<String, std::chrono::year> ); |
| static_assert( parsable<String, std::chrono::year, String> ); |
| static_assert( parsable<String, std::chrono::year, minutes> ); |
| static_assert( parsable<String, std::chrono::year, String, minutes> ); |
| static_assert( parsable<const char*, std::chrono::year, String> ); |
| static_assert( parsable<const char*, std::chrono::year, String, minutes> ); |
| // Mismatched allocators |
| static_assert( not parsable<string, std::chrono::year, String> ); |
| static_assert( not parsable<string, std::chrono::year, String, minutes> ); |
| static_assert( not parsable<String, std::chrono::year, string> ); |
| static_assert( not parsable<String, std::chrono::year, string, minutes> ); |
| } |
| |
| void |
| test_whitespace() |
| { |
| using namespace std::chrono_literals; |
| std::chrono::minutes min; |
| std::istringstream is; |
| is.str(" a b 1 "); |
| is >> parse(" a b %M", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 1min ); |
| is.str(" a b 1 "); |
| is >> parse(" a b %M ", min); |
| VERIFY( is.eof() && !is.fail() ); |
| VERIFY( min == 1min ); |
| is.clear(); |
| is.str(" 1"); |
| is >> parse(" %n%M%n", min); |
| VERIFY( is.fail() ); |
| is.clear(); |
| is.str(" a b 1 "); |
| is >> parse("%n a%n%nb %t%M%n", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 1min ); |
| is.str("a b 1 "); |
| is >> parse("%ta b %M%n%t", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 1min ); |
| is.str("1 "); |
| is >> parse("%M%n%t%t", min); |
| VERIFY( is.eof() && !is.fail() ); |
| VERIFY( min == 1min ); |
| is.clear(); |
| is.str("1 "); |
| is >> parse("%M%n%t%t", min); |
| VERIFY( is.eof() && !is.fail() ); |
| VERIFY( min == 1min ); |
| is.clear(); |
| is.str("1 "); |
| is >> parse("%M%n%t%n", min); |
| VERIFY( is.eof() && is.fail() ); |
| VERIFY( min == 1min ); |
| } |
| |
| void |
| test_errors() |
| { |
| using namespace std::chrono_literals; |
| std::chrono::minutes min(999); |
| std::chrono::year y(-1); |
| std::istringstream is; |
| |
| is.str("x"); |
| is >> parse("x", min); // Matches expected pattern, but no minutes present. |
| VERIFY( !is.eof() && is.fail() ); |
| VERIFY( min == 999min ); |
| |
| is.clear(); |
| is.str("x"); |
| is >> parse("%M", min); // Doesn't match expected pattern. |
| VERIFY( !is.eof() && is.fail() ); |
| VERIFY( min == 999min ); |
| |
| is.clear(); |
| is.str("001:002"); |
| is >> parse("%H:%M", min); // Extracts "00" then fails to find ':' next. |
| VERIFY( !is.eof() && is.fail() ); |
| VERIFY( min == 999min ); |
| |
| is.clear(); |
| is.str("12:61"); |
| is >> parse("%H:%M", min); // 61min is out of range. |
| VERIFY( !is.eof() && is.fail() ); |
| VERIFY( min == 999min ); |
| |
| is.clear(); |
| is.str("12:15 100"); |
| is >> parse("%H:%M %3y", min); // 100y is out of range for %y but not needed |
| VERIFY( is.good() ); |
| VERIFY( min == (12h + 15min) ); |
| |
| min = 999min; |
| is.clear(); |
| is.str("12:15 100"); |
| is >> parse("%H:%M %3y", y); // 100y is out of range for %y and needed |
| VERIFY( is.fail() ); |
| VERIFY( y == -1y ); |
| |
| is.clear(); |
| is.str("23:61 10"); |
| is >> parse("%H:%M %3y", y); // 61min is out of range but not needed |
| VERIFY( is.eof() && ! is.fail() ); |
| VERIFY( y == 2010y ); |
| |
| min = -1min; |
| is.clear(); |
| is.str("25:59"); |
| is >> parse("%H:%M", min); // 25h is out of range and needed |
| VERIFY( is.fail() ); |
| VERIFY( min == -1min ); |
| |
| is.clear(); |
| is.str("328 00"); |
| is >> parse("%3C %y", y); // 328 is out of range for %C (PR libstdc++/111162) |
| VERIFY( is.fail() ); |
| VERIFY( y == 2010y ); |
| |
| is.clear(); |
| is.str("-328 00"); |
| is >> parse("%3C %y", y); // -328 is out of range for %C |
| VERIFY( is.fail() ); |
| VERIFY( y == 2010y ); |
| } |
| |
| void |
| test_modifiers() |
| { |
| using namespace std::chrono_literals; |
| std::chrono::minutes min; |
| std::istringstream is; |
| |
| is.str("0001:000002"); |
| is >> parse("%4H:%5M", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 60min ); |
| |
| is.str("0001:000002"); |
| is >> parse("%6H:%6M", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 62min ); |
| |
| is.str("002"); |
| is >> parse("%4M", min); |
| VERIFY( is.eof() && !is.fail() ); |
| VERIFY( min == 2min ); |
| |
| is.clear(); |
| is.str("0061"); |
| is >> parse("%3M", min); |
| VERIFY( is.good() ); |
| VERIFY( min == 6min ); |
| |
| is.clear(); |
| is.str("0061"); |
| is >> parse("%4M", min); |
| VERIFY( !is.eof() && is.fail() ); |
| |
| is.clear(); |
| is.str("0061"); |
| is >> parse("%5M", min); |
| VERIFY( is.eof() && is.fail() ); |
| |
| std::chrono::seconds s; |
| is.clear(); |
| is.str("000000000012345"); |
| is >> parse("%12S", s); // Read more than 10 digits to check overflow logic. |
| VERIFY( is.good() ); |
| VERIFY( s == 12s ); |
| } |
| |
| int main() |
| { |
| test_recommended_practice(); |
| test_adl(); |
| test_whitespace(); |
| test_errors(); |
| test_modifiers(); |
| } |