我怎样才能允许将向量传递给 INFO()、CAPTURE()、WARN() 等,同时避免非法扩展 std 名称空间?

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

在 Catch Unit Test v1.8.1 中,使用 gcc 6.2.0,我试图在测试失败时通过将向量传递给 INFO(...) 或 [=15= 来方便地输出向量的内容].为此,我重载了流插入运算符:

#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 );
            }
        }
    }
}

如果我(非法)像上面那样扩展 std 命名空间,那么它就可以工作,并且我看到了所需的输出:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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

但是为了合法,当我尝试将 operator<< 重载移出 std 命名空间并移入全局命名空间(通过注释掉 #define THIS_WORKS_BUT_EXTENDING_NAMESPACE_STD_IS_ILLEGAL)时,由于将向量传递给 CAPTURE() 宏,代码无法编译。

根据 Catch docs,我尝试用 Catch::toString 重载替换 operator << 重载:

#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();
    }
}

Catch::StringMaker 专长:

#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();
        }
    }; 
}

但在任何一种情况下,由于将向量传递给 CAPTURE() 宏,测试仍然无法编译。

Catch 文档说将 operator<< 重载放入与您的类型相同的命名空间,但 std::vector 不是我的类型,并且将该重载放入命名空间 std 是非法的.

但我找到的唯一方法是让 CAPTURE()(或 INFO()、或 WARN() 等)接受 std::vector 参数是非法将 operator<< 重载放入命名空间 std.

是否有适当、合法的方法来做到这一点?

我想我找到了有效的答案。 (编辑:请参阅 以获得更好的解决方案。)

不是将 operator<< 重载放入 std 命名空间,而是将其放入 Catch 命名空间编译并给出所需的行为:

namespace Catch {

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

}

Catch docs 说要将 operator<< 重载放入与您的类型相同的名称空间中:

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;
   }

但是由于 std::vector 不是 my 类型,并且它存在于命名空间 std 中,所以我无法按照文档所说的去做.

那么可以将 operator<< 重载放入 Catch 命名空间吗?它有效,但是可以吗?如果我这样做会发生坏事吗?文档确实说可以将 toString 的重载放入 Catch 命名空间,那么 operator<< 重载也可以吗?

我想我找到了一些比 更好的解决方案:


解决方案 1:

将 Catch 更新到 v1.8.2 或更高版本。从一些快速测试来看,v1.8.2 似乎在 CAPTURE 宏中添加了对 std::vector 的支持,您无需付出任何额外的努力。在这种情况下,不需要为 std::vector 重载 operator<<


解决方案 2:

如果您出于某种原因无法更新到 Catch v1.8.2 或更新版本,此解决方案类似于我在原始问题中提出的解决方案,但基于 C++ 委员会成员 Jonathan 的 进行了改进Wakely(谢谢!)。

他给出了以下建议:

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 );
            }
        }
    }
}

这在 Catch v1.8.1 上编译和运行,并给出以下输出:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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