2

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?

Quokka
  • 174
  • 1
  • 3
  • 10
  • have you tried using templates? e.g. like [this](http://melpon.org/wandbox/permlink/V3PBxGOEXblD9Q1B) – W.F. Mar 06 '17 at 09:32
  • @WhozCraig That's a typo on my part. Fixing original post. – Quokka Mar 06 '17 at 15:56
  • @W.F. I just tried that, and it has the same problem. The test doesn't compile with that function template unless that function template is placed into the std namespace. But since your function template isn't a specialization of a function template that already exists in namespace std, it's illegal to put it into the std namespace, according to [this page on cppreference.com](http://en.cppreference.com/w/cpp/language/extending_std). – Quokka Mar 07 '17 at 03:45

2 Answers2

0

I think I found an answer that works. (EDIT: See the other answer for better solutions.)

Instead of putting the operator<< overload into the std namespace, putting it into the Catch namespace compiles and gives the desired behavior:

namespace Catch {

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

}

The Catch docs say to put the operator<< overload into the same namespace as your type:

operator<< overload for std::ostream

This is the standard way of providing string conversions in C++ - and the chances are you may already provide this for your own purposes. If you're not familiar with this idiom it involves writing a free function of the form:

   std::ostream& operator<<( std::ostream& os, T const& value ) {
       os << convertMyTypeToString( value );
       return os;
   }

(where T is your type and convertMyTypeToString is where you'll write whatever code is necessary to make your type printable - it doesn't have to be in another function).

You should put this function in the same namespace as your type. [Emphasis mine]

Alternatively you may prefer to write it as a member function:

   std::ostream& T::operator<<( std::ostream& os ) const {
       os << convertMyTypeToString( *this );
       return os;
   }

But since std::vector isn't my type, and it lives in namespace std, I can't do what the docs say to do.

So is it okay to put the operator<< overload into the Catch namespace instead? It works, but is it okay? Will bad things happen if I do? The docs do say that it's okay to put overloads of toString into the Catch namespace, so does that make it okay for operator<< overloads as well?

Quokka
  • 174
  • 1
  • 3
  • 10
0

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
Community
  • 1
  • 1
Quokka
  • 174
  • 1
  • 3
  • 10