From c2bc8dae2eced94e8c1103218b052dace48bce55 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Wed, 25 Oct 2023 04:03:39 -0400 Subject: [PATCH] Stream builder notation for strings. This should be less boilerplate than repeated `lexical_cast` or `stringify` calls when building strings. --- IOStreams/OStreamable.test/0.cc | 23 +++++++++++-- IOStreams/Stream.h | 61 +++++++++++++++++++++++++++++++++ ProgramOptions.h | 5 +-- Testing/TableTest.h | 8 +++-- tuplize_args.h | 14 ++++---- 5 files changed, 98 insertions(+), 13 deletions(-) create mode 100644 IOStreams/Stream.h diff --git a/IOStreams/OStreamable.test/0.cc b/IOStreams/OStreamable.test/0.cc index 21486b0..bb962e2 100644 --- a/IOStreams/OStreamable.test/0.cc +++ b/IOStreams/OStreamable.test/0.cc @@ -10,6 +10,7 @@ static_assert( __cplusplus > 2020'00 ); #include #include +#include namespace { @@ -27,7 +28,7 @@ namespace auto - stringify( const Agg &agg, const std::string delim ) + stringify_specific( const Agg &agg, const std::string delim ) { std::ostringstream oss; Alepha::IOStreams::setGlobalFieldDelimiter( "YOU SHOULD NOT SEE THIS" ); @@ -58,6 +59,7 @@ static auto init= Alepha::Utility::enroll <=[] { using namespace Alepha::Testing::exports; using namespace Alepha::Testing::literals::test_literals; + using namespace Alepha::IOStreams::exports::stream; "Simple OStream (default delimiter)"_test <=TableTest< stringify_default > ::Cases @@ -65,7 +67,24 @@ static auto init= Alepha::Utility::enroll <=[] { "smoke test", { { 1, 2, 3 } }, { "1\t2\t3" } }, }; - "Simple OStream (specific delimiter)"_test <=TableTest< stringify > + "Simple OStream (specific delimiter)"_test <=TableTest< stringify_specific > + ::Cases + { + { "smoke test", { { 1, 2, 3 }, "\t" }, { "1\t2\t3" } }, + { "smoke test", { { 1, 2, 3 }, "," }, { "1,2,3" } }, + { "smoke test", { { 1, 2, 3 }, ";;" }, { "1;;2;;3" } }, + { "smoke test", { { 1, 2, 3 }, ", " }, { "1, 2, 3" } }, + }; + + "Simple OStream (stream builder)"_test <=TableTest + < + []( const Agg agg, const std::string delim ) + { + using Alepha::IOStreams::Stream; + using Alepha::IOStreams::setFieldDelimiter; + return Stream{} << setFieldDelimiter( delim ) << agg << FinishString; + } + > ::Cases { { "smoke test", { { 1, 2, 3 }, "\t" }, { "1\t2\t3" } }, diff --git a/IOStreams/Stream.h b/IOStreams/Stream.h new file mode 100644 index 0000000..d5f0026 --- /dev/null +++ b/IOStreams/Stream.h @@ -0,0 +1,61 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include + +#include + +namespace Alepha::Hydrogen::IOStreams ::detail:: stream +{ + inline namespace exports + { + class Stream; + + enum { FinishString }; + + std::string stringify( const Alepha::OStreamable auto &item, Alepha::OStreamable auto && ... params ); + } + + class exports::Stream + { + private: + // TODO: We need the exception throwing capabilities of the + // `boost::lexical_cast` operation. But this stream technique + // lets us build strings using stream modifiers and manipulators, + // which `boost::lexical_cast` doesn't support. + std::ostringstream oss; + + public: + Stream && + operator << ( const Alepha::OStreamable auto &t ) && + { + oss << t; + return std::move( *this ); + } + + std::string + operator << ( decltype( FinishString ) ) && + { + return std::move( oss ).str(); + } + + operator std::string () && + { + return std::move( *this ) << FinishString; + } + }; + + inline std::string + exports::stringify( const Alepha::OStreamable auto &item, Alepha::OStreamable auto && ... params ) + { + return ( Stream{} << ... << params ) << item << FinishString; + } +} + +namespace Alepha::Hydrogen::IOStreams::inline exports::inline stream +{ + using namespace detail::stream::exports; +} diff --git a/ProgramOptions.h b/ProgramOptions.h index 23a5b0b..de678f7 100644 --- a/ProgramOptions.h +++ b/ProgramOptions.h @@ -89,10 +89,11 @@ static_assert( __cplusplus > 2020'00 ); #include -#include #include #include +#include + #include namespace Alepha::inline Cavorite ::detail:: program_options @@ -265,7 +266,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options // variable's value in C++ at runtime. auto defaultBuilder= [&value] { - auto text= boost::lexical_cast< std::string >( value ); + auto text= IOStreams::stringify( value ); if( text.find_first_of( " \n\t" ) != std::string::npos ) { diff --git a/Testing/TableTest.h b/Testing/TableTest.h index 942deaa..890418d 100644 --- a/Testing/TableTest.h +++ b/Testing/TableTest.h @@ -29,6 +29,8 @@ static_assert( __cplusplus > 2020'00 ); #include #include +#include + #include #include @@ -122,7 +124,7 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test } else if constexpr( Meta::is_ostreamable_v< T > ) { - return boost::lexical_cast< std::string >( v ); + return IOStreams::stringify( v ); } else if constexpr( Meta::is_optional_v< T > ) { @@ -269,11 +271,11 @@ namespace Alepha::Hydrogen::Testing ::detail:: table_test const auto result= witness == expected; if( not result ) { - std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; + std::cout << " " << C::testFail << "FAILED CASE" << resetStyle << ": " << comment << std::endl; ++failureCount; printDebugging< outputMode >( witness, expected ); } - else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; + else std::cout << " " << C::testPass << "PASSED CASE" << resetStyle << ": " << comment << std::endl; } return failureCount; diff --git a/tuplize_args.h b/tuplize_args.h index b01d3c9..704e0b7 100644 --- a/tuplize_args.h +++ b/tuplize_args.h @@ -12,10 +12,12 @@ static_assert( __cplusplus > 2020'00 ); #include #include #include -#include +#include #include +#include + #include "meta.h" #include "error.h" #include "Concepts.h" @@ -48,10 +50,10 @@ namespace Alepha::Hydrogen ::detail:: tuplize_args explicit ArityMismatchError( const std::size_t remaining, const std::size_t processed, const std::string &clarification= "" ) : remaining_( remaining ), processed_( processed ), clarification( clarification ), - message( ( clarification.empty() ? "" : ( clarification + ": " ) ) - + "Argument count mismatch. " - + boost::lexical_cast< std::string >( remaining ) + " remaining " - + boost::lexical_cast< std::string >( processed ) + " processed" ) {} + message( IOStream::Stream{} << ( clarification.empty() ? "" : ( clarification + ": " ) ) + << "Argument count mismatch. " + << remaining << " remaining " + << processed j< " processed" ) {} const char * @@ -109,7 +111,7 @@ namespace Alepha::Hydrogen ::detail:: tuplize_args const std::vector< std::string > rv; std::transform( begin( args ) + offset, end( args ), back_inserter( rv ), - boost::lexical_cast< type, std::string > ); + IOStreams::stringify< type > ); return std::tuple_cat( std::tuple{ arv }, tuplizeArgsBackend( tail{}, args, offset + rv.size() ) ); } else