problems with overload resolution and operator<

2019-05-10 03:54发布

问题:

Given the following code:

#include <string>
#include <type_traits>
#include <sstream>
#include <vector>
#include <iostream>
using namespace std;

namespace has_insertion_operator_impl {
    typedef char no;
    typedef char yes[2];

    struct any_t {
        template <typename T>
        any_t(T const&);
    };

    no operator<<(ostream const&, any_t const&);

    yes& test(ostream&);
    no   test(no);

    template <typename T>
    struct has_insertion_operator {
        static ostream& s;
        static T const&      t;
        static bool const    value = sizeof(test(s << t)) == sizeof(yes);
    };
}

template <typename T>
struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator<T> {};

template <class T>
typename enable_if<has_insertion_operator<T>::value, string>::type stringify(const T& in) {
    stringstream stream;
    stream << in;
    return stream.str();
}

template <class T>
typename enable_if< ! has_insertion_operator<T>::value, string>::type stringify(const T&) {
    return "{?}";
}

// ======= OVERLOADS PROVIDED BY THE USER =======

template<typename T, typename T2>
struct myType { T data; T2 op; };

template<typename T, typename T2>
ostream& operator<<(ostream& s, const myType<T, T2>&) { s << "myType"; return s; }

template<typename T>
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }

template<typename T, typename A>
ostream& operator<<(ostream& s, const vector<T, A>&) { s << "vector<T, A>"; return s; }

int main() {
    myType<int, float> a;   cout << stringify(a) << endl; // prints "myType"
                            cout << stringify(6) << endl; // prints "6"
    vector<int> v(5);       cout << stringify(v) << endl; // prints "{?}"

    return 0;
}

Why does the template myType<> get stringified but the templated vector<> type doesn't?

For the vector<> type I get the default {?} stringification but I clearly want one of the overloads at the bottom to be called - just like with myType<>

EDIT:

The actual question here is Why is has_insertion_operator<vector<int>> false?

I also need this in C++98

And the operator<< overloads should be supplied after stringify() - like with myType<>

回答1:

I think the problem is with the lookup and I will provide my understanding.

When you call the operator<< in your has... struct argument dependent lookup kicks in and since myType lives in the same namespace that the overloaded operator<< it gets founded and you get a correct string. But when you try to output a vector it tries to search for the overloaded operator<< by the same argument-dependent lookup rules and fails to do it since there are no overloaded operators in std namespace. So it falls back to the unqualified search which starts from the namespace where the call is made, hence it finds your stub operator<<

So in order to fix it you might have placed operator<< overloads to the std namespace(which is forbidden by the Standard) or remove your namespace — it will give the same effect.

You don't have to put everything out of the private namespace, though. It is enough to do something like this in the global namespace:

typedef char no;
typedef char yes[2];
template<typename T>
no operator<<(ostream const&, T const&);

Or, if it is possible, it is better to require the library users to put their overloads into the same namespaces where their classes live. It will not work with std members, though.



回答2:

There are two problems here.

The first is that something in the has_insertion_operator<> predicate is amiss.

I replaced it with this one...

template<class T>
struct has_insertion_operator
{
    template<class U>
    static auto test(U*p) -> decltype((std::declval<std::ostream>() << (*p)), void(), std::true_type());

    template<class U>
    static auto test(...) -> std::false_type;

    static constexpr bool value = decltype(test<T>(0))::value;
};

... which fixed that problem, and highlighted the next (which is perhaps more serious for you):

./stringify.cpp:73:12: error: call to function 'operator<<' that is neither visible in the template definition nor found by argument-dependent lookup
    stream << in;
           ^
./stringify.cpp:100:37: note: in instantiation of function template specialization 'stringify<std::__1::vector<int, std::__1::allocator<int> > >' requested here
    vector<int> v(5);       cout << stringify(v) << endl; // prints "{?}"
                                    ^
./stringify.cpp:91:10: note: 'operator<<' should be declared prior to the call site
ostream& operator<<(ostream& s, const vector<T>&) { s << "vector<T>"; return s; }
         ^
1 error generated.

This is because the template function operator<< <std::vector...> is being referenced before it's defined.

once you move the definition of that up above the definition of stringify, everything works.

Final, note:

overloading operator<< for std::vector is a really bad idea. It will cause you all kinds of headaches later on.

But I'm using c++98 and I want users to be able to supply their own overloads [specialisations]

Ok then, let's do it the easy (and more correct) way, which will work for all flavours of c++ and is going to cause no headaches due to invasion of the std namespace with illegal overloads:

#include <string>
#include <sstream>
#include <vector>
#include <iostream>


// define a template class emitter which by default simply calls operator<<
template<class T>
struct emitter
{
    emitter(const T& v) : _v(v) {}
    std::ostream& operator()(std::ostream& os) const {
        return os << _v;
    }
    const T& _v;
};

// emitter<>'s are streamable
template<class T>
std::ostream& operator<<(std::ostream& os, const emitter<T>& e)
{
    return e(os);
}

// a factory function to generate the correct emitter
template<class T>
emitter<T> emit(const T& v)
{
    return emitter<T>(v);
}

// write one version of stringify in terms of emit<>()
template <class T>
std::string stringify(const T& in) {
    std::stringstream stream;
    stream << emit(in);
    return stream.str();
}
// ======= OVERLOADS PROVIDED BY THE USER =======

template<typename T, typename T2>
struct myType { T data; T2 op; };

// user must provide an emitter for each custom type
template<typename T, typename T2>
struct emitter<myType<T, T2> >
{
    typedef myType<T, T2> value_type;
    emitter(const value_type& v) : _v(v) {}

    std::ostream& operator()(std::ostream& os) const
    {
        return os << "myType";
    }
private:
    const value_type& _v;
};

// and for any std:: templates he wants to support
template<class V, class A>
struct emitter<std::vector<V, A> >
{
    typedef std::vector<V, A> value_type;
    emitter(const value_type& v) : _v(v) {}

    std::ostream& operator()(std::ostream& os) const
    {
        return os << "vector<T, A>";
    }
private:
    const value_type& _v;
};

// test
int main() {
    myType<int, float> a;  std::cout << stringify(a) << std::endl; // prints "myType"
                           std::cout << stringify(6) << std::endl; // prints "6"
    std::vector<int> v(5); std::cout << stringify(v) << std::endl; // prints "vector<T, A>"

    return 0;
}