From a47b8dee744165f1f2ce3d184746b3a301f50f8a Mon Sep 17 00:00:00 2001 From: mat tso Date: Mon, 7 Mar 2016 23:22:34 +0100 Subject: [PATCH] Add support toString for containers Standard container types were printed as `"{?}"` (default `toString` implementation for unsupported class). This was contradictory with the documentation: > "Most [...] std types are supported out of the box" when in fact only `string`, `vector` and `tupple` were supported. - Renamed the `toStringVector.cpp` test file to `toStringContainers.cpp` - Types are consider containers if they contain `value_type` and `const_iterator` members and have `begin` and `end` support (members or ADL findable) returning a `const_iterator`. `const_iterator::operator*` must also return a `const value_type &` - Beware that a trying to printing a type fulfilling those requirements but returning invalid iterators will results in undefined behaviour. In such case specialize the Catch::Detail::IsContainer trait to contain `static const bool value = false` to revert to the default behaviour (printing `"{?}"`). Test pretty printing of `std::list`, `std::deque`, `std::forward_list`, `std::array` in Catch assertion macro. More complex structure like `std::queue` or `std::multi_map` should also be tested. Signed-off-by: mat tso --- include/internal/catch_tostring.h | 98 +++++++++++++-- projects/SelfTest/ToStringContainers.cpp | 149 +++++++++++++++++++++-- 2 files changed, 228 insertions(+), 19 deletions(-) diff --git a/include/internal/catch_tostring.h b/include/internal/catch_tostring.h index 4a981d59..0c030869 100644 --- a/include/internal/catch_tostring.h +++ b/include/internal/catch_tostring.h @@ -13,8 +13,12 @@ #include #include #include -#include #include +#include + +#ifndef CATCH_CPP11_OR_GREATER +#include +#endif #ifdef __OBJC__ #include "catch_objc_arc.hpp" @@ -24,7 +28,7 @@ #include #endif -#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#ifdef CATCH_CPP11_OR_GREATER #include #endif @@ -76,6 +80,8 @@ namespace Detail { template BorgType( T const& ); }; + // Without c++11's `decltype`, rely on function overloading returning + // different sized types that can be discriminated with `sizeof`. struct TrueType { char sizer[1]; }; struct FalseType { char sizer[2]; }; @@ -174,18 +180,75 @@ 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() ); -// } -//}; - +#ifndef CATCH_CPP11_OR_GREATER template std::string toString( std::vector const& v ) { return Detail::rangeToString( v.begin(), v.end() ); } +#else + +namespace Detail { +// Importing c++11 std instead of prefixing call by std:: to allow for user +// custom overload find through ADL +using std::begin; +using std::end; + +template +struct HasBasicContainerApi +{ + /// This overload will be chosen if all the operations in it's signature + /// can be resolved at compile time. + template()), + // Test that begin and end return comparable values + class = decltype(begin(std::declval()) + != end(std::declval()))> + static std::true_type has( typename C::const_iterator*, typename C::value_type*); + /// Lower priority overload will be picked if the previous one fails. + template + static std::false_type has( ... ); + static const bool value = decltype(has(NULL, NULL))::value; +}; + +// 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 { + // C array are containers although not having the standard api. + static const bool value = std::is_array::value; +}; + +// Specialization for types that have a basic container API +// that can now be checked for type inconsistency. +template +struct IsContainer { +private: + typedef typename C::value_type value_type; + typedef typename C::const_iterator const_iterator; + static const C& c; +public: + static const bool value = + std::is_same::value && + std::is_same::value && + std::is_same::value && + std::is_same::value; +}; + +/// Print containers by printing their elements in succession +template::value>::type, + class = void> +std::string toStringInternal( C const& c ) { + return Detail::rangeToString( begin(c), end(c) ); +} + +} // end namespace Detail + +#endif // CATCH_CPP11_OR_GREATER + // toString for pairs template struct StringMaker > { @@ -247,6 +310,19 @@ 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 +#endif + > +std::string toStringInternal( T const& value ) { + return StringMaker::convert( value ); +} + } // end namespace Detail /// \brief converts any type to a string @@ -254,11 +330,11 @@ namespace Detail { /// The default template forwards on to ostringstream - except when an /// ostringstream overload does not exist - in which case it attempts to detect /// that and writes {?}. -/// Overload (not specialise) this template for custom typs that you don't want +/// Overload (not specialize) this template for custom types that you don't want /// to provide an ostream overload for. template std::string toString( T const& value ) { - return StringMaker::convert( value ); + return Detail::toStringInternal(value); } diff --git a/projects/SelfTest/ToStringContainers.cpp b/projects/SelfTest/ToStringContainers.cpp index c0dbceaa..fd0cc37a 100644 --- a/projects/SelfTest/ToStringContainers.cpp +++ b/projects/SelfTest/ToStringContainers.cpp @@ -1,7 +1,13 @@ #include "catch.hpp" #include + +// Containers are only pretty print in c++11 +#if defined(CATCH_CPP11_OR_GREATER) #include #include +#include +#include +#endif /// \file Test Catch::to_string for standard containors. @@ -30,14 +36,6 @@ struct SequenceTest { } }; -// vector -TEST_CASE( "vector -> toString", "[toString][containers][vector]" ) { - SequenceTest::integers(); -} -TEST_CASE( "vector -> toString", "[toString][containers][vector]" ) { - SequenceTest::strings(); -} - namespace { /** \brief Custom allocator, should not impact toString. */ template @@ -53,9 +51,144 @@ namespace { } // vector +TEST_CASE( "vector -> toString", "[toString][containers][vector]" ) { + SequenceTest::integers(); +} +TEST_CASE( "vector -> toString", "[toString][containers][vector]" ) { + SequenceTest::strings(); +} TEST_CASE( "vector -> toString", "[toString][containers][vector][allocator]" ) { SequenceTest::integers(); } TEST_CASE( "vector -> toString", "[toString][containers][vector][allocator]" ) { SequenceTest::strings(); } +TEST_CASE( "vector -> toString", "[toString][containers][vector][allocator]" ) { + std::vector bools; + REQUIRE( Catch::toString(bools) == "{ }"); + bools.push_back(true); + REQUIRE( Catch::toString(bools) == "{ true }"); + bools.push_back(false); + REQUIRE( Catch::toString(bools) == "{ true, false }"); +} + +#if defined(CATCH_CPP11_OR_GREATER) + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++98-compat" +#endif + +// list +TEST_CASE( "list -> toString", "[toString][containers][list]" ) { + SequenceTest::integers(); +} +TEST_CASE( "list -> toString", "[toString][containers][list]" ) { + SequenceTest::strings(); +} +TEST_CASE( "list -> toString", "[toString][containers][list][allocator]" ) { + SequenceTest::integers(); +} +TEST_CASE( "list -> toString", "[toString][containers][list][allocator]" ) { + SequenceTest::strings(); +} + +// deque +TEST_CASE( "deque -> toString", "[toString][containers][deque]" ) { + SequenceTest::integers(); +} +TEST_CASE( "deque -> toString", "[toString][containers][deque]" ) { + SequenceTest::strings(); +} +TEST_CASE( "deque -> toString", "[toString][containers][deque][allocator]" ) { + SequenceTest::integers(); +} +TEST_CASE( "deque -> toString", "[toString][containers][deque][allocator]" ) { + SequenceTest::strings(); +} + + +// C array +TEST_CASE( "int [N] -> toString", "[toString][containers][c-array]" ) { + // Arrays of size 0 can not exist in c++ + int oneValue[] = { 42 }; + REQUIRE( Catch::toString(oneValue) == "{ 42 }" ); + int twoValues[] = { 42, 250 }; + REQUIRE( Catch::toString(twoValues) == "{ 42, 250 }" ); +} + +TEST_CASE( "string [N] -> toString", "[toString][containers][c-array]" ) { + std::string oneValue[] = { "hello" }; + REQUIRE( Catch::toString(oneValue) == "{ \"hello\" }" ); + std::string twoValues[] = { "hello", "world" }; + REQUIRE( Catch::toString(twoValues) == "{ \"hello\", \"world\" }" ); +} + +TEST_CASE( "char [N] -> toString", "[toString][c-string]" ) { + // Do not consider char[] as containers but rather as c-string + char emptyCString[] = ""; + REQUIRE( Catch::toString(emptyCString) == "\"\"" ); + char cstring[] = "hello"; + REQUIRE( Catch::toString(cstring) == "\"hello\"" ); +} + +// 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