From 83719b17a6a827db6125c74beb788738594b4f2a Mon Sep 17 00:00:00 2001 From: Mark Gates Date: Mon, 5 Sep 2022 14:10:29 -0400 Subject: [PATCH] For enums, use to_string, from_string. Requires C++17. --- CMakeLists.txt | 4 +- test/ref/101.txt | 2 +- test/test.cc | 15 +++- test/test.hh | 5 +- testsweeper.hh | 228 ++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 204 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1833aa9..cb391c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,8 +122,8 @@ if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") testsweeper PRIVATE TESTSWEEPER_ID="${testsweeper_id}" ) endif() -# Use and export -std=c++11; don't allow -std=gnu++11 extensions. -target_compile_features( testsweeper PUBLIC cxx_std_11 ) +# Use and export -std=c++17; don't allow -std=gnu++17 extensions. +target_compile_features( testsweeper PUBLIC cxx_std_17 ) set_target_properties( testsweeper PROPERTIES CXX_EXTENSIONS false ) if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.15) diff --git a/test/ref/101.txt b/test/ref/101.txt index edd83f2..ff2e955 100644 --- a/test/ref/101.txt +++ b/test/ref/101.txt @@ -1,5 +1,5 @@ -Error: --type: invalid value 'x' +Error: --type: invalid datatype 'x' TestSweeper version NA, id NA input: ./tester --type 's,x,d' sort Usage: test [-h|--help] diff --git a/test/test.cc b/test/test.cc index 1f7ac2f..c99c3dd 100644 --- a/test/test.cc +++ b/test/test.cc @@ -94,14 +94,21 @@ Params::Params(): cache ( "cache", 0, ParamType::Value, 20, 1, 1024, "total cache size, in MiB" ), // ----- routine parameters - // name, w, p, type, default, char2enum, enum2char, enum2str, help - datatype_old + // name, w, p, type, default, help + datatype ( "type", 4, ParamType::List, DataType::Double, + "One of: s, r32, single, float; d, r64, double; c, c32, complex; z, c64, complex; i, int, integer" ), + + #ifdef DEPRECATED + // name, w, p, type, default, char2enum, enum2char, enum2str, help + datatype_old ( "type-old", 4, ParamType::List, DataType::Double, char2datatype, datatype2char, datatype2str, "s=single (float), d=double, c=complex, z=complex, i=int" ), - // name, w, p, type, default, str2enum, enum2str, help - datatype ( "type", 4, ParamType::List, DataType::Double, str2datatype, datatype2str, + // name, w, p, type, default, str2enum, enum2str, help + datatype_old2 + ( "type", 4, ParamType::List, DataType::Double, str2datatype, datatype2str, "One of: s, r32, single, float; d, r64, double; c, c32, complex; z, c64, complex; i, int, integer" ), + #endif // name, w, type, default, min, max, help nb ( "nb", 3, ParamType::List, 32, 0, INT64_MAX, "block size" ), diff --git a/test/test.hh b/test/test.hh index b45ba3c..4dfb149 100644 --- a/test/test.hh +++ b/test/test.hh @@ -28,8 +28,11 @@ public: testsweeper::ParamInt cache; // ----- routine parameters - testsweeper::ParamEnum< testsweeper::DataType > datatype_old; testsweeper::ParamEnum< testsweeper::DataType > datatype; + #ifdef DEPRECATED + testsweeper::ParamEnum< testsweeper::DataType > datatype_old; + testsweeper::ParamEnum< testsweeper::DataType > datatype_old2; + #endif testsweeper::ParamInt nb; testsweeper::ParamInt3 dim; testsweeper::ParamInt3 grid; diff --git a/testsweeper.hh b/testsweeper.hh index 130a13c..1f90696 100644 --- a/testsweeper.hh +++ b/testsweeper.hh @@ -15,6 +15,7 @@ #include #include #include +#include // Version is updated by make_release.py; DO NOT EDIT. // Version 2023.11.05 @@ -53,6 +54,10 @@ public: // ----------------------------------------------------------------------------- void throw_error( const char* format, ... ); + +// ============================================================================= +// Enums + // ----------------------------------------------------------------------------- enum class DataType { Integer = 'i', @@ -63,32 +68,23 @@ enum class DataType { DoubleComplex = 'z', }; -// ---------------------------------------- -// Accepts: s (single/float), d (double), c (complex-single), z (complex-double), -// i (int) -inline DataType char2datatype( char ch ) -{ - ch = tolower( ch ); - if (ch != 'i' && ch != 'h' && ch != 's' && ch != 'd' && ch != 'c' && ch != 'z') { - throw_error( "invalid value '%c'", ch ); - } - return DataType( ch ); -} - -// ---------------------------------------- -inline char datatype2char( DataType en ) -{ - return char( en ); -} - -// ---------------------------------------- -/// Accepts a variety of inputs: -/// { i, i32, int, integer } => int -/// { s, r32, float, single } => float -/// { d, r64, double } => double -/// { c, c32, complex, complex-float, complex-single } => complex -/// { z, c64, complex, complex-double } => complex -inline DataType str2datatype( const char* str ) +//---------------------------------------- +/// Convert string to DataType enum. Accepts a variety of inputs: +/// +/// { i, i32, int, integer } => Integer +/// +/// { h, r16, half } => Half +/// { s, r32, float, single } => Float +/// { d, r64, double } => Double +/// { c, c32, complex } => FloatComplex +/// { z, c64, complex } => DoubleComplex +/// +/// Single letters come from traditional BLAS names (i, s, d, c, z) +/// and a few commonly accepted new ones (h, q). +/// The r32, c32, etc. come from XBLAS proposal, +/// https://bit.ly/blas-g2-proposal +/// +inline DataType from_string( std::string const& str, DataType dummy ) { std::string str_ = str; if (str_ == "i" @@ -114,16 +110,51 @@ inline DataType str2datatype( const char* str ) || str_ == "c64" || str_ == "complex" || str_ == "complex-double") return DataType::DoubleComplex; - else { - throw_error( "invalid value '%s'", str ); - return DataType(0); + else + throw_error( "invalid datatype '%s'", str.c_str() ); + return DataType::Integer; +} + +//-------------------- +[[deprecated("Use from_string. Remove 2025-02.")]] +inline DataType char2datatype( char ch ) +{ + ch = tolower( ch ); + if (ch != 'i' && ch != 's' && ch != 'd' && ch != 'c' && ch != 'z') { + throw_error( "invalid datatype '%c'", ch ); } + return DataType( ch ); +} + +//-------------------- +[[deprecated("Use from_string. Remove 2025-02.")]] +inline DataType str2datatype( const char* str ) +{ + return from_string( str, DataType() ); +} + +//---------------------------------------- +/// +inline const char* to_c_str( DataType value ) +{ + switch (value) { + case DataType::Integer: return "i"; + case DataType::Half: return "h"; + case DataType::Single: return "s"; + case DataType::Double: return "d"; + case DataType::SingleComplex: return "c"; + case DataType::DoubleComplex: return "z"; + } + throw_error( "invalid datatype" ); } -// ---------------------------------------- -inline const char* datatype2str( DataType en ) +//---------------------------------------- +/// Convert DataType enum to C-style string representation. +/// Temporary common low-level implementation for to_string and datatype2str. +/// +inline const char* to_c_string( DataType value ) { - switch (en) { + switch (value) { case DataType::Integer: return "i"; case DataType::Half: return "h"; case DataType::Single: return "s"; @@ -131,9 +162,86 @@ inline const char* datatype2str( DataType en ) case DataType::SingleComplex: return "c"; case DataType::DoubleComplex: return "z"; } - return ""; + throw_error( "invalid datatype" ); + return "?"; } +//-------------------- +/// Convert DataType enum to C++ string representation. +/// +inline std::string to_string( DataType value ) +{ + return std::string( to_c_string( value ) ); +} + +//-------------------- +[[deprecated("Use to_string. Remove 2025-02.")]] +inline const char* datatype2str( DataType value ) +{ + // Can't write datatype2str using return to_string( value ).c_str() + // since string is on stack. + return to_c_string( value ); +} + +//-------------------- +[[deprecated("Use to_string. Remove 2025-02.")]] +inline char datatype2char( DataType value ) +{ + return to_c_string( value )[ 0 ]; +} + + +//============================================================================== +// Utilities + +//------------------------------------------------------------------------------ +/// Use SFINAE to test for existence of from_string( string, T* ) function. +template +class has_from_string +{ +private: + /// Matches from_string( string str, T* val ); return type is void. + template + static auto test( std::string str, T2 val ) + -> decltype( from_string( str, T2() ) ); + + /// Matches everything else; return type is void (something other than T). + template + static void test(...); + +public: + // True if from_string( string, type ) exists, based on void return type. + static const bool value = std::is_same< + T, + decltype( test( std::string(), T() ) ) + >::value; +}; + +//------------------------------------------------------------------------------ +/// Use SFINAE to test for existence of to_string( T ) function. +/// This relies on ADL; it doesn't check the std namespace, so it won't +/// work for basic types (int, float, etc.) -- not sure how to do that. +template +class has_to_string +{ +private: + /// Matches to_string( T val ); return type is string. + template + static auto test( T2 val ) + -> decltype( to_string( val ) ); + + /// Matches everything else; return type is int (something other than string). + template + static int test(...); + +public: + // True if from_string( string, type ) exists, based on string return type. + static const bool value = std::is_same< + std::string, + decltype( test( T() ) ) + >::value; +}; + // ----------------------------------------------------------------------------- int scan_range( const char **strp, int64_t *start, int64_t *end, int64_t *step ); int scan_range( const char **strp, double *start, double *end, double *step ); @@ -650,8 +758,19 @@ public: typedef ENUM (*str2enum)( const char* str ); typedef const char* (*enum2str)( ENUM en ); - // deprecated - // Takes char2enum, enum2char, enum2str. + /// Constructor for enums that have to_string and from_string. + ParamEnum( const char* name, int width, ParamType type, + ENUM default_value, + const char* help ): + TParamBase( name, width, type, default_value, help ), + char2enum_( nullptr ), + enum2char_( nullptr ), + str2enum_ ( nullptr ), + enum2str_ ( nullptr ) + {} + + /// Deprecated constructor taking char2enum, enum2char, enum2str. + [[deprecated("Use constructor without char2enum, etc. Remove 2025-02.")]] ParamEnum( const char* name, int width, ParamType type, ENUM default_value, char2enum in_char2enum, enum2char in_enum2char, @@ -664,7 +783,8 @@ public: enum2str_( in_enum2str ) {} - // Takes str2enum, enum2str. + /// Deprecated constructor taking str2enum, enum2str. + [[deprecated("Use constructor without char2enum, etc. Remove 2025-02.")]] ParamEnum( const char* name, int width, ParamType type, ENUM default_value, str2enum in_str2enum, enum2str in_enum2str, @@ -681,6 +801,7 @@ public: virtual void help() const; protected: + // deprecated: char2enum_, etc.; remove 2025-02. char2enum char2enum_; enum2char enum2char_; str2enum str2enum_; @@ -703,10 +824,14 @@ void ParamEnum::parse( const char *str ) str += len; // Parse word into enum. str2enum_ & char2enum_ throw errors. ENUM val; - if (str2enum_) { + if constexpr (has_from_string::value) { + val = from_string( buf, ENUM() ); + } + else if (str2enum_) { val = str2enum_( buf ); } else { + assert( char2enum_ != nullptr ); val = char2enum_( buf[0] ); } this->push_back( val ); @@ -726,8 +851,17 @@ template void ParamEnum::print() const { if (this->used_ && this->width_ > 0) { - printf( "%*s ", this->width_, - this->enum2str_( this->values_[ this->index_ ] )); + if constexpr (has_to_string::value) { + printf( "%*s ", this->width_, + to_string( this->values_[ this->index_ ] ).c_str() ); + } + else if (enum2str_) { + printf( "%*s ", this->width_, + this->enum2str_( this->values_[ this->index_ ] )); + } + else { + throw_error( "no to_string method available" ); + } } } @@ -737,9 +871,19 @@ template void ParamEnum::help() const { if (this->type_ == ParamType::Value || this->type_ == ParamType::List) { - printf( " %-16s %s; default %s\n", - this->option_.c_str(), this->help_.c_str(), - this->enum2str_( this->default_value_ )); + if constexpr (has_to_string::value) { + printf( " %-16s %s; default %s\n", + this->option_.c_str(), this->help_.c_str(), + to_string( this->default_value_ ).c_str() ); + } + else if (enum2str_) { + printf( " %-16s %s; default %s\n", + this->option_.c_str(), this->help_.c_str(), + this->enum2str_( this->default_value_ )); + } + else { + throw_error( "no to_string method available" ); + } } }