diff --git a/include/span.h b/include/span.h index 6ee810d..8151737 100644 --- a/include/span.h +++ b/include/span.h @@ -51,8 +51,8 @@ #define GSL_MSVC_HAS_VARIADIC_CTOR_BUG #define GSL_MSVC_NO_SUPPORT_FOR_MOVE_CTOR_DEFAULT -// noexcept is not understood -#ifndef GSL_THROWS_ON_CONTRACT_VIOLATION +// noexcept is not understood +#ifndef GSL_THROW_ON_CONTRACT_VIOLATION #pragma push_macro("noexcept") #define noexcept /* nothing */ #endif diff --git a/include/string_span.h b/include/string_span.h index 250c528..b11dd9e 100644 --- a/include/string_span.h +++ b/include/string_span.h @@ -23,15 +23,37 @@ #include "span.h" #include -// VS 2013 workarounds #ifdef _MSC_VER + +// No MSVC does constexpr fully yet +#pragma push_macro("constexpr") +#define constexpr /* nothing */ + +// VS 2013 workarounds #if _MSC_VER <= 1800 #define GSL_MSVC_HAS_TYPE_DEDUCTION_BUG +// noexcept is not understood +#ifndef GSL_THROW_ON_CONTRACT_VIOLATION +#pragma push_macro("noexcept") +#define noexcept /* nothing */ +#endif + #endif // _MSC_VER <= 1800 #endif // _MSC_VER +// In order to test the library, we need it to throw exceptions that we can catch +#ifdef GSL_THROW_ON_CONTRACT_VIOLATION + +#ifdef _MSC_VER +#pragma push_macro("noexcept") +#endif + +#define noexcept /* nothing */ + +#endif // GSL_THROW_ON_CONTRACT_VIOLATION + namespace gsl { // @@ -56,27 +78,6 @@ using zstring = char*; template using wzstring = wchar_t*; -// -// string_span and relatives -// -// Note that Extent is always single-dimension only -// -template -using basic_string_span = span; - -template -using string_span = basic_string_span; - -template -using cstring_span = basic_string_span; - -template -using wstring_span = basic_string_span; - -template -using cwstring_span = basic_string_span; - - // // ensure_sentinel() // @@ -86,7 +87,7 @@ using cwstring_span = basic_string_span; // Will fail-fast if sentinel cannot be found before max elements are examined. // template -span ensure_sentinel(const T* seq, std::ptrdiff_t max = PTRDIFF_MAX) +span ensure_sentinel(T* seq, std::ptrdiff_t max = PTRDIFF_MAX) { auto cur = seq; while ((cur - seq) < max && *cur != Sentinel) ++cur; @@ -101,46 +102,341 @@ span ensure_sentinel(const T* seq, std::ptrdiff_t max = PTRDIF // the limit of size_type. // template -inline basic_string_span ensure_z(T* const & sz, std::ptrdiff_t max = PTRDIFF_MAX) +inline span ensure_z(T* const & sz, std::ptrdiff_t max = PTRDIFF_MAX) { return ensure_sentinel(sz, max); } // TODO (neilmac) there is probably a better template-magic way to get the const and non-const overloads to share an implementation -inline basic_string_span ensure_z(char* const& sz, std::ptrdiff_t max) +inline span ensure_z(char* const& sz, std::ptrdiff_t max) { auto len = strnlen(sz, max); Ensures(sz[len] == 0); return{ sz, static_cast(len) }; } -inline basic_string_span ensure_z(const char* const& sz, std::ptrdiff_t max) +inline span ensure_z(const char* const& sz, std::ptrdiff_t max) { auto len = strnlen(sz, max); Ensures(sz[len] == 0); return{ sz, static_cast(len) }; } -inline basic_string_span ensure_z(wchar_t* const& sz, std::ptrdiff_t max) +inline span ensure_z(wchar_t* const& sz, std::ptrdiff_t max) { auto len = wcsnlen(sz, max); Ensures(sz[len] == 0); return{ sz, static_cast(len) }; } -inline basic_string_span ensure_z(const wchar_t* const& sz, std::ptrdiff_t max) +inline span ensure_z(const wchar_t* const& sz, std::ptrdiff_t max) { auto len = wcsnlen(sz, max); Ensures(sz[len] == 0); return{ sz, static_cast(len) }; } template -basic_string_span ensure_z(T(&sz)[N]) { return ensure_z(&sz[0], static_cast(N)); } +span ensure_z(T(&sz)[N]) { return ensure_z(&sz[0], static_cast(N)); } template -basic_string_span::type, dynamic_range> ensure_z(Cont& cont) +span::type, dynamic_range> ensure_z(Cont& cont) { return ensure_z(cont.data(), static_cast(cont.length())); } + +// TODO (neilmac) there is probably a better template-magic way to get the const and non-const overloads to share an implementation +inline span remove_z(char* const& sz, std::ptrdiff_t max) +{ + auto len = strnlen(sz, max); + return{ sz, static_cast(len) }; +} + +inline span remove_z(const char* const& sz, std::ptrdiff_t max) +{ + auto len = strnlen(sz, max); + return{ sz, static_cast(len) }; +} + +inline span remove_z(wchar_t* const& sz, std::ptrdiff_t max) +{ + auto len = wcsnlen(sz, max); + return{ sz, static_cast(len) }; +} + +inline span remove_z(const wchar_t* const& sz, std::ptrdiff_t max) +{ + auto len = wcsnlen(sz, max); + return{ sz, static_cast(len) }; +} + +template +span remove_z(T(&sz)[N]) +{ + return remove_z(&sz[0], static_cast(N)); +} + +template +span::type, dynamic_range> remove_z(Cont& cont) +{ + return remove_z(cont.data(), static_cast(cont.length())); +} + +template +class basic_string_span; + +namespace details +{ + template + struct is_basic_string_span_oracle : std::false_type + {}; + + template + struct is_basic_string_span_oracle> : std::true_type + {}; + + template + struct is_basic_string_span : is_basic_string_span_oracle> + {}; +} + +// +// string_span and relatives +// +// Note that Extent is always single-dimension only +// +template +class basic_string_span +{ + using value_type = CharT; + using const_value_type = std::add_const_t; + using pointer = std::add_pointer_t; + using reference = std::add_lvalue_reference_t; + using const_reference = std::add_lvalue_reference_t; + using bounds_type = static_bounds; + using underlying_type = span; + +public: + using size_type = ptrdiff_t; + using iterator = typename underlying_type::iterator; + using const_iterator = typename underlying_type::const_iterator; + using reverse_iterator = typename underlying_type::reverse_iterator; + using const_reverse_iterator = typename underlying_type::const_reverse_iterator; + + // empty + constexpr basic_string_span() noexcept + : real(nullptr) + {} + + // copy + constexpr basic_string_span(const basic_string_span& other) noexcept + : real(other.real) + {} + + // move + constexpr basic_string_span(const basic_string_span&& other) noexcept + : real(std::move(other.real)) + {} + + // from nullptr and length + constexpr basic_string_span(nullptr_t ptr, size_type length) noexcept + : real(ptr, length) + {} + + // For pointers and static arrays - if 0-terminated, remove 0 from the view + + // from c string + + constexpr basic_string_span(pointer& ptr) noexcept + : real(ensure_z(ptr)) + {} + + // from non-const pointer to const span + template::value>> + constexpr basic_string_span(std::remove_const_t*& ptr) noexcept + : real(ensure_z(ptr)) + {} + + // from raw data and length - remove 0 if needed + constexpr basic_string_span(pointer ptr, size_type length) noexcept + : real(remove_z(ptr, length)) + {} + + // from static arrays and string literals + template + constexpr basic_string_span(value_type(&arr)[N]) noexcept + : real(remove_z(arr)) + {} + + // Those allow 0s in the middle, so we keep them + + constexpr basic_string_span(std::string& s) noexcept + : real(&(s.at(0)), static_cast(s.length())) + {} + + // from containers. It must have .size() and .data() function signatures + template ::value + && !details::is_basic_string_span::value + && !(!std::is_const::value && std::is_const::value) // no converting const containers to non-const span + && std::is_convertible::value + && std::is_same().size(), *std::declval().data())>, DataType>::value> + > + constexpr basic_string_span(Cont& cont) + : real(cont.data(), cont.size()) + {} + + // from span + template , + typename Dummy = std::enable_if_t::value && std::is_convertible::value> + > + constexpr basic_string_span(const span& other) noexcept + : real(other) + {} + + // from string_span + template , + typename Dummy = std::enable_if_t::value && std::is_convertible::value> + > + constexpr basic_string_span(const basic_string_span& other) noexcept + : real(other.data(), other.length()) + {} + + // section on linear space + template + constexpr basic_string_span first() const noexcept + { + return{ real.first() }; + } + + constexpr basic_string_span first(size_type count) const noexcept + { + return{ real.first(count); } + } + + template + constexpr basic_string_span last() const noexcept + { + return{ real.last() }; + } + + constexpr basic_string_span last(size_type count) const noexcept + { + return{ real.last(count); } + } + + template + constexpr basic_string_span sub() const noexcept + { + return{ real.sub() }; + } + + constexpr basic_string_span sub(size_type offset, size_type count = dynamic_range) const noexcept + { + return{ real.sub(offset, count) }; + } + + constexpr const_reference operator[](size_type idx) const noexcept + { + return real[idx]; + } + + constexpr reference operator[](size_type idx) noexcept + { + return real[idx]; + } + + constexpr pointer data() const noexcept + { + return real.data(); + } + + constexpr size_type length() const noexcept + { + return real.size(); + } + + constexpr size_type size() const noexcept + { + return real.size(); + } + + constexpr size_type used_length() const noexcept + { + return length(); + } + + constexpr size_type bytes() const noexcept + { + return real.bytes(); + } + + constexpr size_type used_bytes() const noexcept + { + return bytes(); + } + + constexpr explicit operator bool() const noexcept + { + return real; + } + + constexpr iterator begin() const noexcept + { + return real.begin(); + } + + constexpr iterator end() const noexcept + { + return real.end(); + } + + constexpr const_iterator cbegin() const noexcept + { + return real.cbegin(); + } + + constexpr const_iterator cend() const noexcept + { + real.cend(); + } + + constexpr reverse_iterator rbegin() const noexcept + { + return real.rbegin(); + } + + constexpr reverse_iterator rend() const noexcept + { + return real.rend(); + } + + constexpr const_reverse_iterator crbegin() const noexcept + { + return real.crbegin(); + } + + constexpr const_reverse_iterator crend() const noexcept + { + return real.crend(); + } + +private: + span real; +}; + +template +using string_span = basic_string_span; + +template +using cstring_span = basic_string_span; + +template +using wstring_span = basic_string_span; + +template +using cwstring_span = basic_string_span; + // // to_string() allow (explicit) conversions from string_span to string // @@ -213,14 +509,89 @@ template using wzstring_builder = basic_zstring_builder; } + +constexpr bool operator==(const gsl::cstring_span<>& one, const gsl::cstring_span<>& other) noexcept +{ + return std::equal(one.begin(), one.end(), other.begin(), other.end()); +} + +constexpr bool operator==(const gsl::string_span<>& one, const gsl::string_span<>& other) noexcept +{ + return std::equal(one.begin(), one.end(), other.begin(), other.end()); +} + + +// TODO: ca we make twmplate ops work? +//template +//constexpr bool operator==(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +//{ +// return std::equal(one.begin(), one.end(), other.begin(), other.end()); +//} +/* +template , std::remove_cv_t>::value>> +constexpr bool operator==(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return std::equal(one.begin(), one.end(), other.begin(), other.end()); +} + +template , std::remove_cv_t>::value>> +constexpr bool operator!=(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return !(one == other); +} + +template , std::remove_cv_t>::value>> +constexpr bool operator<(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return std::lexicographical_compare(one.begin(), one.end(), other.begin(), other.end()); +} + +template , std::remove_cv_t>::value>> +constexpr bool operator<=(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return !(other < one); +} + +template , std::remove_cv_t>::value>> +constexpr bool operator>(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return other < one; +} + +template , std::remove_cv_t>::value>> +constexpr bool operator>=(const gsl::basic_string_span& one, const gsl::basic_string_span& other) noexcept +{ + return !(one < other); +} +*/ // VS 2013 workarounds #ifdef _MSC_VER + +#undef constexpr +#pragma pop_macro("constexpr") + #if _MSC_VER <= 1800 -#undef GSL_MSVC_HAS_TYPE_DEDUCTION_BUG +#pragma warning(pop) + +#ifndef GSL_THROW_ON_CONTRACT_VIOLATION +#undef noexcept +#pragma pop_macro("noexcept") +#endif // GSL_THROW_ON_CONTRACT_VIOLATION + +#undef GSL_MSVC_HAS_TYPE_DEDUCTION_BUG #endif // _MSC_VER <= 1800 #endif // _MSC_VER +#if defined(GSL_THROW_ON_CONTRACT_VIOLATION) + +#undef noexcept + +#ifdef _MSC_VER +#pragma pop_macro("noexcept") +#endif + +#endif // GSL_THROW_ON_CONTRACT_VIOLATION #endif // GSL_STRING_SPAN_H diff --git a/tests/string_span_tests.cpp b/tests/string_span_tests.cpp index f14df93..08faaa8 100644 --- a/tests/string_span_tests.cpp +++ b/tests/string_span_tests.cpp @@ -18,6 +18,8 @@ #include #include #include +#include +#include using namespace std; using namespace gsl; @@ -27,7 +29,7 @@ SUITE(string_span_tests) { TEST(TestLiteralConstruction) - { + { cwstring_span<> v = ensure_z(L"Hello"); CHECK(5 == v.length()); @@ -35,7 +37,7 @@ SUITE(string_span_tests) #ifdef CONFIRM_COMPILATION_ERRORS wstring_span<> v2 = ensure0(L"Hello"); #endif - } + } TEST(TestConstructFromStdString) { @@ -51,8 +53,8 @@ SUITE(string_span_tests) CHECK(v.length() == static_cast::size_type>(vec.size())); } - TEST(TestStackArrayConstruction) - { + TEST(TestStackArrayConstruction) + { wchar_t stack_string[] = L"Hello"; { @@ -62,7 +64,7 @@ SUITE(string_span_tests) { cwstring_span<> v = stack_string; - CHECK(v.length() == 6); + CHECK(v.length() == 5); } { @@ -72,7 +74,7 @@ SUITE(string_span_tests) { wstring_span<> v = stack_string; - CHECK(v.length() == 6); + CHECK(v.length() == 5); } } @@ -95,7 +97,7 @@ SUITE(string_span_tests) { char stack_string[] = "Hello"; cstring_span<> v = ensure_z(stack_string); - (void)v; + (void)v; #ifdef CONFIRM_COMPILATION_ERRORS string_span<> v2 = v; string_span<> v3 = "Hello"; @@ -113,6 +115,391 @@ SUITE(string_span_tests) CHECK(static_cast::size_type>(s2.length()) == v.length()); CHECK(s2.length() == 5); } + + TEST(ComparisonAndImplicitConstructors) + { + { + cstring_span<> span = "Hello"; + + const char ar[] = { 'H', 'e', 'l', 'l', 'o' }; + const char ar1[] = "Hello"; + const char ar2[10] = "Hello"; + const char* ptr = "Hello"; + const std::string str = "Hello"; + const std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + + // comparison to literal + CHECK(span == cstring_span<>("Hello")); + + // comparison to static array with no null termination + CHECK(span == cstring_span<>(ar)); + + // comparison to static array with null at the end + CHECK(span == cstring_span<>(ar1)); + + // comparison to static array with null in the middle + CHECK(span == cstring_span<>(ar2)); + + // comparison to null-terminated c string + CHECK(span == cstring_span<>(ptr, 5)); + + // comparison to string + CHECK(span == cstring_span<>(str)); + + // comparison to vector of charaters with no null termination + CHECK(span == cstring_span<>(vec)); + + // comparison of the original data to string + CHECK(span.data() == std::string("Hello")); + + CHECK(span == "Hello"); + CHECK(span == ar); + CHECK(span == ar1); + CHECK(span == ar2); + CHECK(span == ptr); + CHECK(span == str); + CHECK(span == vec); + + char _ar[] = { 'H', 'e', 'l', 'l', 'o' }; + char _ar1[] = "Hello"; + char _ar2[10] = "Hello"; + char* _ptr = _ar1; + std::string _str = "Hello"; + std::vector _vec = { 'H', 'e', 'l', 'l', 'o' }; + + CHECK(span == _ar); + CHECK(span == _ar1); + CHECK(span == _ar2); + CHECK(span == _ptr); + CHECK(span == _str); + CHECK(span == _vec); + + string_span<> _span{ _ptr }; + + CHECK(_span == _ar); + CHECK(_span == _ar1); + CHECK(_span == _ar2); + CHECK(_span == _ptr); + CHECK(_span == _str); + CHECK(_span == _vec); + + CHECK(_span == "Hello"); + CHECK(_span == ar); + CHECK(_span == ar1); + CHECK(_span == ar2); + CHECK(_span == ptr); + CHECK(_span == str); + CHECK(_span == vec); + } + + { + std::vector str1 = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> span1 = str1; + std::vector str2 = std::move(str1); + cstring_span<> span2 = str2; + + // comparison of spans from the same vector before and after move (ok) + CHECK(span1 == span2); + } + } + + TEST(EnzureRemoveZ) + { + // remove z from literals + { + cstring_span<> sp = "hello"; + CHECK((sp.length() == 5)); + } + + // take the string as is + { + auto str = std::string("hello"); + cstring_span<> sp = str; + CHECK((sp.length() == 5)); + } + + // ensure z on c strings + { + char* ptr = new char[3]; + + ptr[0] = 'a'; + ptr[1] = 'b'; + ptr[2] = '\0'; + + string_span<> span(ptr); + CHECK(span.length() == 2); + + delete[] ptr; + } + + // ensuze z on c strings + { + char* ptr = new char[2]; + + ptr[0] = 'a'; + ptr[1] = 'b'; + + // do we want to have a constructor from pointer at all? + // the behavior is unpredictable if the string is not 0-terminated + + // CHECK_THROW((string_span<>(ptr).length() == 2), fail_fast); + + cstring_span<> sp1{ ptr, 2 }; // good + cstring_span<> sp2{ ptr, 3 }; // bad... but can't help there + + CHECK(sp1[1] == 'b'); + CHECK_THROW((sp1[2] == 'c'), fail_fast); + + CHECK(sp2[1] == 'b'); + //CHECK_THROW((sp1[2] == 'c'), fail_fast); // buffer overflow + + delete[] ptr; + } + } + + TEST(Constructors) + { + // from string temporary +#ifdef CONFIRM_COMPILATION_ERRORS + { + cstring_span<> span = std::string("Hello"); + } +#endif + + // from string literal + { + cstring_span<> span = "Hello"; + CHECK(span.length() == 5); + } + + // from const static array + { + const char ar[] = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> span = ar; + CHECK(span.length() == 5); + } + + // from non-const static array + { + char ar[] = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> span = ar; + CHECK(span.length() == 5); + } + + // from const ptr and length + { + const char* ptr = "Hello"; + cstring_span<> span{ ptr, 5 }; + CHECK(span.length() == 5); + } + + // from non-const ptr and length + { + char* ptr = "Hello"; + cstring_span<> span{ ptr, 5 }; + CHECK(span.length() == 5); + } + + // from const string + { + const std::string str = "Hello"; + cstring_span<> span = str; + CHECK(span.length() == 5); + } + + // from non-const string + { + std::string str = "Hello"; + cstring_span<> span = str; + CHECK(span.length() == 5); + } + + // from const vector + { + const std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> span = vec; + CHECK(span.length() == 5); + } + + // from non-const vector + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> span = vec; + CHECK(span.length() == 5); + } + + // from const span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + const span inner = vec; + cstring_span<> span = inner; + CHECK(span.length() == 5); + } + + // from non-const span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + span inner = vec; + cstring_span<> span = inner; + CHECK(span.length() == 5); + } + + // from const string_span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> tmp = vec; + cstring_span<> span = tmp; + CHECK(span.length() == 5); + } + + // from non-const string_span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> tmp = vec; + cstring_span<> span = tmp; + CHECK(span.length() == 5); + } + + /////////////////////////////////////////////////// + // How string_span should behave with const data + + // from string literal + { +#ifdef CONFIRM_COMPILATION_ERRORS + string_span<> span = "Hello"; + CHECK(span.length() == 5); +#endif + } + + // from const static array + { +#ifdef CONFIRM_COMPILATION_ERRORS + const char ar[] = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> span = ar; + CHECK(span.length() == 5); +#endif + } + + // from non-const static array + { + char ar[] = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> span = ar; + CHECK(span.length() == 5); + } + + // from const ptr and length + { +#ifdef CONFIRM_COMPILATION_ERRORS + const char* ptr = "Hello"; + string_span<> span{ ptr, 5 }; + CHECK(span.length() == 5); +#endif + } + + // from non-const ptr and length + { + char* ptr = "Hello"; + string_span<> span{ ptr, 5 }; + CHECK(span.length() == 5); + } + + // from const string + { +#ifdef CONFIRM_COMPILATION_ERRORS + const std::string str = "Hello"; + string_span<> span = str; + CHECK(span.length() == 5); +#endif + } + + // from non-const string + { + std::string str = "Hello"; + string_span<> span = str; + CHECK(span.length() == 5); + } + + // from const vector + { +#ifdef CONFIRM_COMPILATION_ERRORS + const std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> span = vec; + CHECK(span.length() == 5); +#endif + } + + // from non-const vector + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> span = vec; + CHECK(span.length() == 5); + } + + // from const span + { +#ifdef CONFIRM_COMPILATION_ERRORS + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + const span inner = vec; + string_span<> span = inner; + CHECK(span.length() == 5); +#endif + } + + // from non-const span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + span inner = vec; + string_span<> span = inner; + CHECK(span.length() == 5); + } + + // from non-const span of non-const data from const vector (looks like a bug) + { +#ifdef CONFIRM_COMPILATION_ERRORS + const std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + const span inner = vec; // fix error (happens inside the constructor) + string_span<> span = inner; + CHECK(span.length() == 5); +#endif + } + + // from const string_span + { +#ifdef CONFIRM_COMPILATION_ERRORS + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + cstring_span<> tmp = vec; + string_span<> span = tmp; + CHECK(span.length() == 5); +#endif + } + + // from non-const string_span + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> tmp = vec; + string_span<> span = tmp; + CHECK(span.length() == 5); + } + + // from non-const string_span from const vector + { +#ifdef CONFIRM_COMPILATION_ERRORS + const std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + string_span<> tmp = vec; + string_span<> span = tmp; + CHECK(span.length() == 5); +#endif + } + + // from const string_span of non-const data + { + std::vector vec = { 'H', 'e', 'l', 'l', 'o' }; + const string_span<> tmp = vec; // what does "const span" mean? + string_span<> span = tmp; + CHECK(span.length() == 5); + } + } + } int main(int, const char *[])