Enhance existing matcher system
Consider the following snippet:
struct event { std::string message; std::any data; };
auto events = std::vector<event> { ... };
auto found = std::find_if(events.begin(), events.end(), [](auto event) { return event.message == "task started"; });
CHECK(found);While this gets the job done, it bugs me for a few reasons:
- We need to use
<algorithm>forstd::find_ifwhich incurs an not-insignificant inclusion cost - The majority of the
auto found = ...expression is iterative boilerplate - Upon assertion failure, we learn nothing about the content of
events(assuming a stringification is available for botheventandstd::vector)- This is particularly annoying when a failure occurs on CI/CD, where debugging is not readily available
- This could be fixed via an explicit
CAPTURE(events), but this feels like a bolted-on solution
Taking inspiration from RSpec, we could rewrite this into the following hypothetical form:
using namespace doctest::matchers;
CHECK(events == Includes( Splat("task started", ignore) ));The Includes takes a matcher<T> object, and essentially performs the std::find_if operation. The Splat on the other hand, takes an Args... sequence which are the members of T, destructs the T object into its parts, and then pairwise compares each (note this only works if T is POD), effectively performing the lambda associated with the std::find_if. doctest::ignore is a special matcher which is always-true. This would resolve the three prior points:
- It is directly available in
doctest, at (presumably) a low inclusion/compilation cost - The assertion is now almost-entirely composed of relevant components
- Upon assertion failure, the expression decomposer will request
eventsto be shown, providing further context when debugging
Matchers would be an entirely optional part of doctest to make certain types of assertion easier, but should have no impact on existing code-bases.
Source: doctest/doctest