diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index f1301dcc07..9b6a2d36bf 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -13,8 +13,8 @@ #include #include #include -#include #include +#include #ifdef __OBJC__ #include "catch_objc_arc.hpp" @@ -174,18 +174,100 @@ namespace Detail { std::string rangeToString( InputIterator first, InputIterator last ); } -//template -//struct StringMaker > { -// static std::string convert( std::vector const& v ) { -// return Detail::rangeToString( v.begin(), v.end() ); -// } -//}; +// Implementation detail for constant containers detection -template -std::string toString( std::vector const& v ) { - return Detail::rangeToString( v.begin(), v.end() ); +namespace Detail { + +/// C++11 enable_if +template + struct enable_if {}; +template + struct enable_if { typedef T type; }; + +/// C++11 std::declval +template +const T &declval(); + +/// C++11 std::begin +template +typename C::const_iterator begin(C&c) { + return c.begin(); +} + +/// C++11 std::end +template +typename C::const_iterator end(C& c) { + return c.end(); +} + +// Without c++11's `decltype`, rely on function overloading returning different +// sized types that can be discriminated with `sizeof`. +struct No { char v[1]; }; // Will never be instantiated +struct Yes { char v[10]; }; // Will never be instantiated + +template +struct HasBasicContainerApi +{ + /// This overload will be chosen if all the operations in it's signature + /// can be resolved at compile time. + template()) != end(declval()))> + static Yes has(typename C::const_iterator *, typename C::value_type *); + /// Lower priority overload will be picked if the previous one fails. + template + static No has(...); + static const bool value = sizeof(has(NULL, NULL)) == sizeof(Yes); +}; + +// Compile type function that returns true if a type matches +// the container concept (http://en.cppreference.com/w/cpp/concept/Container), +// false otherwise. +template ::value> +struct IsContainer { + static const bool value = false; // By default a type is not a container +}; + +// Pre `decltype` trick to test the type of an expression +template +struct IsSame : No {}; +template +struct IsSame : Yes {}; + +template +IsSame isOfType(const Right &) { return IsSame(); }; + +#define INTERNAL_CATCH_IS_OF_TYPE(TYPE, VALUE) \ + (sizeof(isOfType(VALUE)) == sizeof(Yes)) + +// Specialisation for types that have a basic container api +// that can now be checked for type inconsistency. +template +struct IsContainer { + typedef typename C::value_type value_type; + typedef typename C::const_iterator const_iterator; + + static const bool value = + INTERNAL_CATCH_IS_OF_TYPE(const_iterator, begin(declval())) && + INTERNAL_CATCH_IS_OF_TYPE(const_iterator, end(declval())) && + INTERNAL_CATCH_IS_OF_TYPE(value_type, *begin(declval())) && + INTERNAL_CATCH_IS_OF_TYPE(value_type, *end(declval())); +}; +#undef INTERNAL_CATCH_IS_OF_TYPE + +/// Print containers by printing their elements in succession +template::value>::type, + // Additional template parameter to discriminate + // the two template toStringInternal overloads. + class = void> +std::string toStringInternal( Container const& c ) { + return Detail::rangeToString( c.begin(), c.end() ); } +} // end namespace Detail + + + // toString for pairs template struct StringMaker > { @@ -247,6 +329,16 @@ namespace Detail { std::string makeString( T const& value ) { return StringMaker::convert( value ); } + +/// Instead of adding complex template overloading of public toString method, +/// use an internal dispatcher which template can get as complicated as needed +/// without impacting the public api. +template::value>::type> +std::string toStringInternal( T const& value ) { + return StringMaker::convert( value ); +} + } // end namespace Detail /// \brief converts any type to a string @@ -258,23 +350,23 @@ namespace Detail { /// to provide an ostream overload for. template std::string toString( T const& value ) { - return StringMaker::convert( value ); + return Detail::toStringInternal(value); } - namespace Detail { - template - std::string rangeToString( InputIterator first, InputIterator last ) { - std::ostringstream oss; - oss << "{ "; - if( first != last ) { - oss << Catch::toString( *first ); - for( ++first ; first != last ; ++first ) - oss << ", " << Catch::toString( *first ); - } - oss << " }"; - return oss.str(); +namespace Detail { +template +std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); } + oss << " }"; + return oss.str(); +} } } // end namespace Catch diff --git a/projects/SelfTest/ToStringContainers.cpp b/projects/SelfTest/ToStringContainers.cpp index c0dbceaa2b..faca67be14 100644 --- a/projects/SelfTest/ToStringContainers.cpp +++ b/projects/SelfTest/ToStringContainers.cpp @@ -3,6 +3,11 @@ #include #include +#if defined(CATCH_CPP11_OR_GREATER) +#include +#include +#endif + /// \file Test Catch::to_string for standard containors. /// \brief Test for sequence containors @@ -38,6 +43,22 @@ TEST_CASE( "vector -> toString", "[toString][containers][vector]" ) { SequenceTest::strings(); } +// deque +TEST_CASE( "deque -> toString", "[toString][containers][deque]" ) { + SequenceTest::integers(); +} +TEST_CASE( "deque -> toString", "[toString][containers][deque]" ) { + SequenceTest::strings(); +} + +// list +TEST_CASE( "list -> toString", "[toString][containers][list]" ) { + SequenceTest::integers(); +} +TEST_CASE( "list -> toString", "[toString][containers][list]" ) { + SequenceTest::strings(); +} + namespace { /** \brief Custom allocator, should not impact toString. */ template @@ -59,3 +80,87 @@ TEST_CASE( "vector -> toString", "[toString][containers][vector][ TEST_CASE( "vector -> toString", "[toString][containers][vector][allocator]" ) { SequenceTest::strings(); } + +// deque +TEST_CASE( "deque -> toString", "[toString][containers][deque][allocator]" ) { + SequenceTest::integers(); +} +TEST_CASE( "deque -> toString", "[toString][containers][deque][allocator]" ) { + SequenceTest::strings(); +} + +// list +TEST_CASE( "list -> toString", "[toString][containers][list][allocator]" ) { + SequenceTest::integers(); +} +TEST_CASE( "list -> toString", "[toString][containers][list][allocator]" ) { + SequenceTest::strings(); +} + +#if defined(CATCH_CPP11_OR_GREATER) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#endif + +// array +TEST_CASE( "array -> toString", "[toString][containers][array]" ) { + std::array empty; + REQUIRE( Catch::toString(empty) == "{ }" ); + std::array oneValue = { 42 }; + REQUIRE( Catch::toString(oneValue) == "{ 42 }" ); + std::array twoValues = { 42, 250 }; + REQUIRE( Catch::toString(twoValues) == "{ 42, 250 }" ); +} + +TEST_CASE( "array -> toString", "[toString][containers][array]" ) { + std::array empty; + REQUIRE( Catch::toString(empty) == "{ }" ); + std::array oneValue = { "hello" }; + REQUIRE( Catch::toString(oneValue) == "{ \"hello\" }" ); + std::array twoValues = { "hello", "world" }; + REQUIRE( Catch::toString(twoValues) == "{ \"hello\", \"world\" }" ); +} + +/// \brief Specialization for `forward_list` to use +// `push_front` instead of the unsupported `push_back`. +template