New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Catch::toString support for containers #606
Add Catch::toString support for containers #606
Conversation
In the first patch I move the
This is a first draft although I hope to eventually add support for all C++ containers.
|
include/internal/catch_tostring.h
Outdated
std::string toString( std::list<T,Allocator> const& c ) { | ||
return Detail::rangeToString( c.begin(), c.end() ); | ||
} | ||
// Associative containors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove comment, patch not done yet.
96ecbed
to
8ced86d
Compare
An other drawback of the Those extra inclusion might not have a big impact tough. I'll need to benchmark it. |
After some extensive testing, my conclusion is that the inclusion and overload for all those containers is negligible. I could not measure a > 4ms overhead. |
include/internal/catch_tostring.h
Outdated
template<typename T, std::size_t N> | ||
std::string toString( std::array<T,N> const& c ) { | ||
return Detail::rangeToString( c.begin(), c.end() ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To implement all 17 standard containers, it might be more readable to use a temporary macro. Though the template commas are going to be quite painful to handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A generic container toString with enable_if begin/end-checks does not work?
Would also remove the need to include all the headers.
template <typename T>
std::string toString(const T& c )
{
return Detail::rangeToString(c.begin(), c.end());
}
While I was adding a |
Why not template<class Container>
std::string toString(Container const& c) {
return Detail::rangeToString( begin(c), end(c) );
} This oughta catch 90% of the cases at least |
@nabijaczleweli Your proposition would work if One solution to reduce this ambiguity would be to use some good old SFINAL: /// Port some C++11 features
namespace detail {
/// C++11 enable_if
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
/// C++11 declval
template <class T>
T &declval();
} // namespace detail
/** Only allow this overload to participate to overload resolution
* if calling `begin` and `end` on a `Container` instance
* does not result in an error.
*/
template<class Container>
detail::enable_if<sizeof(begin(internal::declval<Container>)) ==
sizeof(end(internal::declval<Container>)),
std::string> toString(Container const& c) {
return Detail::rangeToString( begin(c), end(c) );
} This would restrict the toString overload to types with a
To conclude, I do not think that an opt out strategic is a good idea. Still, an opt in declaration could work. // Got lazy, this is C++11 ;)
/// Types are not by default containers
template <class T>
struct IsContainer : std::false_type {};
/// Declare which class are containers
template <class T, class A>
struct IsContainer<std::vector<T, A>> : std::true_type {};
template <class T, class A>
struct IsContainer<std::list<T, A>> : std::true_type {};
...
template <class Ts...>
struct IsContainer<std::map<Ts...>> : std::true_type {};
template<class Container>
std::enable_if<IsContainer<Container>::value,
std::string> toString(Container const& c) {
return Detail::rangeToString( begin(c), end(c) );
} Note that all containers headers must still be included. User could opt in for his custom container as such: namespace Catch { // specialization must occur in primary declaration namespace
struct IsContainer<USER_CUSTOM_CONTAINER_TYPE> : std::true_type {};
} What is your opinion ? |
With the second approach you're still facing a fundamental problem: you need to list every container type. I'd go for a modified approach 1: either (a) require, that there are ADL-accessible
Note: those are |
I'd also go with range-concept checks.
clang and gcc error messages have become quite good, at least when enable_if is in the template parameter list and not in the return type which is generally preferable anyway.
Such a corner case must not impede progress. |
If that is the general consensus, then it will make the implementation very generic and lightweight. Could the maintainer confirm that such corner case should not be handled by design ? @philsquared: Could you confirm that undefined behaviour is acceptable when printing a class with a begin and end method (or ADL accessible) BUT not returning proper iterators. Eg: an instance with |
How about template<class Container>
typename std::enable_if<std::is_base_of<std::forward_iterator_tag,
typename std::iterator_traits<typename Container::iterator>::iterator_category>::value,
std::string>::type toString(Container const& c)
... That should rule out non-containers. |
As far as I know |
8ced86d
to
4cc6393
Compare
4cc6393
to
d6d73da
Compare
Implemented the container auto detection as discussed. It was more complicated than expected, mostly because meta-programing in C++03 is quite harder than with the current standard. Had to remember |
a56f77b
to
2d0a969
Compare
Nice 👍 |
2d0a969
to
1f2a402
Compare
Fixed coding style, and factorized TrueType/FalseType.
I tested the code on windows with cmake (vs2015). 2 tests are broken but not due to my patches, master also has those tests fail. I'll try to fix them. |
Just catching up here! As for C++11 baselining I'm doing this for Catch2, which is already started and I hope to be pushing to a public repo soon(ish). |
oh - and I accepted a PR to fix those failing travis builds just recently (if it's the same one - master is building cleanly now). You might just need to rebase? |
include/internal/catch_tostring.h
Outdated
/// Print containers by printing their elements in succession | ||
template<typename C> | ||
typename Detail::enable_if<Detail::IsContainer<C>::value, std::string>::type | ||
toStringInternal( C const& c ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd pull it out of the return type.
template <typename C,
typename = typename Detail::enable_if<Detail::IsContainer<C>::value, void>::type>
std::string toStringInternal(const C& c)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, function template default parameters were introduced in c++11. Your proposition would not compile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already forgotten about that. Haven't written C++03 in years :D
9fc751e
to
dc961db
Compare
Added support for c arrays. Their sized are deduced at compile time to array therefore any decay would fall back on the plain old pointer display. |
I can no think about any other kind of container to add support for. Consider this PR feature complete ! |
Spook too soon, C++14 is broken due to overload conflicts between the |
Catch::toString support for pairs was implemented and tested but not accessible from the include folder (thus in releases).
Rename projects/SelfTest/ToStringVector.cpp to ToStringContainers.cpp
Exectute allocator tests even for c++98.
Signed-off-by: mat tso <mat-tso@topmail.ie>
dc961db
to
b5a0503
Compare
b5a0503
to
5500d3c
Compare
Found the source of my overload probleme: Also had to restrict my sfinae expression to check for |
If you mean msvc here's their online compiler: http://webcompiler.cloudapp.net/ |
There is still a problem with One solution would be to weaken my Nevertheless the code is already quite complicated. Instead I going to DROP the support for C++03. Only Converting the PR to C++11 will make the patches a lot easier to read and maintain. |
5500d3c
to
3cf712d
Compare
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 <mat-tso@topmail.ie>
3cf712d
to
a47b8de
Compare
Dropped C++03 support of the container detection. Made the code a lot smaller! Hopefully Travis will validate my PR this time. A last review before merge ? |
There seems to be a difference of type on osx of |
What are the chances of getting this updated for Catch2? I don't think I will have much time to help drive this forward, but it would sure be a useful feature to have. |
I've been having a look at this today and had a go at coming up with something myself. I wanted something with fewer bits of template machinery, if possible, and what I've come up with is something that just tests if there's an overload of the Now we can fully assume C++11 this is much easier. Here's a mock up of what I'm thinking: The linked godbolt shows it running against a number of types on a handful of representative compilers. I've also tried this on VS2017 (haven't had a chance on 2015, yet, but I'm reasonably confident). Note that this version explicitly detects arrays as well. I've also put a top level specialisation for |
That looks good. Unfortunately, I do not have the time to do the port but can review it if you want. |
Note that c++11 begin and end support c-array so the specialisation line 19 should not be needed. You need to provide an lvalue to begin and end though. |
BTW, feel free to use my Unit tests (or any other part of my CL), https://github.com/catchorg/Catch2/pull/606/files#diff-43562f40f8c6dcfe2c54557316e0f852. They were pretty helpfull to find inconsistency ( |
@mat-tso - don't worry, I'll be doing the porting part - very happy for you to review, though. |
Oh, and wrt the array detection - I didn't think of using |
Ok, I've checked in my changes in the "stringify" branch for now. |
v2.1.0 is out and with it the support for stringification of ranges (things that respond to requests for iterators). |
In order to enhance
REQUIRE
andCHECK
error message when testing standard containers, add overloads of Catch::toString for:std::pair
std::deque
std::list
std::array
(c++11)std::forward_list
(c++11)(more types upcoming)Those types were printed as
"{?}"
(defaulttoString
implementation for unsupported class). This was contradictory with the documentation:when in fact only
string
,vector
andtupple
were supported.Detail:
toStringVector.cpp
test file totoStringContainers.cpp
and type parametrized thevector
tests to run them also fordeque
andlist
.The overhead of including all the standard container headers is negligable.=> No longer needed, types are treated as containers if they fulfill (some) of the container concept constraints.value_type
andconst_iterator
members and havebegin
andend
support (members or ADL findable) returning aconst_iterator
.const_iterator::operator*
must also return aconst value_type &
static const bool value = false
in order revert to the default behaviour (printing"{?}"
).