Search code examples
c++namespacescatch-unit-test

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?


Solution

  • I think I found some solutions better than the one I gave previously:


    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 or newer 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...

    So with that in mind:

    #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