Quokka Quokka - 11 months ago 64
C++ Question

How can I allow vector to be passed to INFO(), CAPTURE(), WARN(), etc. while avoiding illegally extending the std namespace?

In Catch Unit Test v1.8.1, with gcc 6.2.0, I'm trying to conveniently output the contents of a vector when a test fails by passing the vector to

INFO(...)
or
CAPTURE(...)
. To do so I'm overloading the stream insertion operator:

#include <Catch/single_include/catch.hpp>
#include <vector>
#include <iostream>

#define THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
#ifdef THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
namespace std {
#endif

std::ostream& operator << ( std::ostream& os, const std::vector<int>& v ) {
for ( const auto& e : v ) {
os << e << " ";
}
return os;
}

#ifdef THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
} //namespace std
#endif

int some_operation_on_vector( const std::vector<int>& v ) {
return 1;
}

SCENARIO( "some scenario" )
{
GIVEN( "a vector" )
{
const auto the_vector = std::vector<int>{ 1, 2, 3, 4, 5 };

WHEN( "some result is calculated from the vector" )
{
const auto actual_result = some_operation_on_vector( the_vector );

THEN( "the result should be correct. If not, print out the vector." )
{
const auto expected_result = 0;

CAPTURE( the_vector ); // <--------
//^^^^
//How do I legally make this work?

REQUIRE( expected_result == actual_result );
}
}
}
}





If I (illegally) extend the
std
namespace as above, then it works, and I see the desired output:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catchtestexample is a Catch v1.8.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
Scenario: some scenario
Given: a vector
When: some result is calculated from the vector
Then: the result should be correct. If not, print out the vector.
-------------------------------------------------------------------------------
ExampleTest.cpp:91
...............................................................................

ExampleTest.cpp:95: FAILED:
REQUIRE( expected_result == actual_result )
with expansion:
0 == 1
with message:
the_vector := 1 2 3 4 5

===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed


But to try to be legal, when I try to move the
operator <<
overload out of the
std
namespace and into the global namespace (by commenting out
#define THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL
), the code doesn't compile due to passing a vector to the
CAPTURE()
macro.

Per the Catch docs, I tried replacing the
operator <<
overload with a
Catch::toString
overload:

#include <string>
#include <sstream>

namespace Catch {
std::string toString( const std::vector<int>& v ) {
std::ostringstream ss{};
for ( const auto& e : v ) {
ss << e << " ";
}
return ss.str();
}
}


or with a
Catch::StringMaker
specialisation:

#include <string>
#include <sstream>

namespace Catch {
template<> struct StringMaker<std::vector<int>> {
static std::string convert( const std::vector<int>& v ) {
std::ostringstream ss{};
for ( const auto& e : v ) {
ss << e << " ";
}
return ss.str();
}
};
}


but in either case the test still doesn't compile, due to passing a vector to the
CAPTURE()
macro.

The Catch docs say to put the
operator <<
overload into the same namespace as your type, but
std::vector
is not my type, and putting that overload into namespace
std
is illegal.

But the only way I've been able to find to get
CAPTURE()
(or
INFO()
, or
WARN()
, etc.) to accept a
std::vector
argument is to illegally put the
operator <<
overload into namespace
std
.

Is there a proper, legal way to do this?

Answer Source

I think I found some better solutions:


Solution 1:

Update Catch to v1.8.2 or newer. From some quick tests, it looks like v1.8.2 added support for std::vector in CAPTURE macros, without any extra effort on your part. Overloading operator << for std::vector isn't needed in this case.


Solution 2:

If you can't update to Catch v1.8.2 for whatever reason, this solution is similar to the proposed solution in my original question, but with improvements based on this answer from C++ committee member Jonathan Wakely (Thank you!).

He gives the following advice:

Don't overload operators for types you don't control.

...

Instead create a tiny adaptor class and define the operator for that...

#include <Catch/single_include/catch.hpp>
#include <vector>
#include <iostream>

template <typename T> struct PrintableVector {
    const std::vector<T>& vec_;
};

template <typename T>
PrintableVector<T> makePrintable( const std::vector<T>& vec ) {
    return PrintableVector<T>{ vec };
}

template <typename T>
std::ostream& operator << ( std::ostream& os, const PrintableVector<T>& printableVec ) {
    for ( const auto& e : printableVec.vec_ ) {
        os << e << " ";
    }
    return os;
}

int some_operation_on_vector( const std::vector<int>& v ) {
    return 1;
}

SCENARIO( "some scenario" )
{
    GIVEN( "a vector" )
    {
        const auto the_vector = std::vector<int>{ 1, 2, 3, 4, 5 };

        WHEN( "some result is calculated from the vector" )
        {
            const auto actual_result = some_operation_on_vector( the_vector );

            THEN( "the result should be correct.  If not, print out the vector." )
            {
                const auto expected_result = 0;
                CAPTURE( makePrintable( the_vector ) );
                REQUIRE( expected_result == actual_result );
            }
        }
    }
}

This compiles and runs on Catch v1.8.1, and gives the following output:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
catchtestexample is a Catch v1.8.1 host application.
Run with -? for options

-------------------------------------------------------------------------------
Scenario: some scenario
     Given: a vector
      When: some result is calculated from the vector
      Then: the result should be correct.  If not, print out the vector.
-------------------------------------------------------------------------------
main.cpp:43
...............................................................................

main.cpp:47: FAILED:
  REQUIRE( expected_result == actual_result )
with expansion:
  0 == 1
with message:
  makePrintable( the_vector ) := 1 2 3 4 5 

===============================================================================
test cases: 1 | 1 failed
assertions: 1 | 1 failed
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download