Search code examples
c++c++11googlemockboost-variant

Using std::vector<boost::variant<...>> with Google Mock causes a compilation error


There is this code:

#include "gmock/gmock.h"
#include <boost/variant.hpp>

typedef boost::variant<std::vector<unsigned char>, std::vector<int>> CustomVariant;

// some overloads which don't work
std::ostream& operator<<(
  std::ostream& stream,
  const boost::variant<std::vector<unsigned char>>&)
{ return stream; }

std::ostream& operator<<(
  std::ostream& stream,
  const boost::variant<std::vector<int>>&)
{ return stream; }

std::ostream& operator<<(
  std::ostream& stream,
  const std::vector<unsigned char>&)
{ return stream; }

std::ostream& operator<<(
  std::ostream& stream,
  const std::vector<int>&)
{ return stream; }

class MyClass
{
  MOCK_METHOD1(fun, bool(std::vector<CustomVariant> v));
};

int main()
{
  MyClass a;
  return 0;
}

There are two errors:

/usr/include/boost/variant/detail/variant_io.hpp:64:14: error: no match for ‘operator<<’ (operand types are ‘std::basic_ostream<char>’ and ‘const std::vector<unsigned char>’)
         out_ << operand;
/usr/include/boost/variant/detail/variant_io.hpp:64:14: error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’
         out_ << operand;

It complains that there is not defined operator<< for types std::basic_ostream<char> and const std::vector<unsigned char>, although it seems that it's defined. I tried some overloads but none of them worked. How to properly make this code to compile?

Compiling on g++ 6.3:

g++ main.cpp -lgmock -o main -L ./googletest-release-1.8.0/googlemock -pthread

Solution

  • boost::operator<<(std::ostream&, boost::variant const&) is defined in boost/variant/detail/io.hpp and unfortunately defers to an ADL-found operator<<.

    As mentioned, there is no operator<<(std::ostream&, std::vector<> const&) declared in the std namespace and it's illegal to declare one.

    A workaround is to inject this operator into the boost::detail::variant namespace.

    You wouldn't want to do this in production code because it relies on knowledge of the internals of boost, but in a test it may be acceptable.

    this compiles:

    #include "gmock/gmock.h"
    #include <vector>
    
    struct emitter
    {
        emitter(std::ostream& os) : os(os) {};
    
        template<class T> std::ostream& operator()(T const& v) const
        {
            return os << v;
        }
    
        std::ostream& operator()(char c)
        {
            if (std::isprint(c))
            {
                return os << '\'' + c + '\'';
            }
            else
            {
                auto oldstate = os.flags();
                os << std::hex << "0x" << (int(c) & 0xff);
                os.flags(oldstate);
                return os;
            }
        }
    
    
        template<class T, class A>
        std::ostream &operator()(const std::vector<T, A> &v) const
        {
            const char* sep = " ";
            os << "[";
            for (auto&& x : v)
            {
                (*this)(x);
                sep = ", ";
            }
            return os << " ]";
        }
    
    
        std::ostream& os;
    };
    
    namespace boost { namespace detail { namespace variant {
        template<class T, class A>
        std::ostream &operator<<(std::ostream &os, std::vector<T, A>const &var)
        {
            auto e = emitter(os);
            return e(var);
        }
    
    }}}
    
    #include <boost/variant.hpp>
    #include <iomanip>
    
    
    
    
    typedef boost::variant<std::vector<unsigned char>, std::vector<int>> CustomVariant;
    
    
    
    
    
    class MyClass
    {
        MOCK_METHOD1(fun, bool(std::vector<CustomVariant>v));
    };
    
    int main()
    {
        MyClass a;
        return 0;
    }