5

I'm trying to find a working type trait to detect if a given type has a left shift operator overload for a std::ostream (e.g. interoperable with std::cout or boost::lexical_cast). I've had success with boost::has_left_shift except for the cases where the type is an STL container of POD or std::string types. I suspect this has to do with the specializations of either the STL types or the operator<< functions. What is the correct method for generically identifying types with a valid left shift operator for std::ostream? If that is not feasible, is there a separate method for detecting an overload of the left shift operator on STL containers of POD or std::string types?

The code below shows what I'm currently working with, and demonstrates how boost::has_left_shift fails to detect the overloaded operator<< function even though it is called on the next line. The program compiles and works in GCC 4.5.1 or higher and clang 3.1.

To circumvent the obvious responses, I have tried replacing the templated operator<< function with specific versions for the various types used to no avail. I've also tried various combinations of const-ness and l-value/r-value specifiers for the two types (various tweaks lead me to a compiler message pointing at an operator<< overload with a r-value ostream). I've also tried implementing my own trait which at best gives me the same results as boost::has_left_shift.

Thanks in advance for any help that can be provided. I would also greatly appreciate it if a thorough explanation of why this behavior is occurring and how the solution works can be included. I'm stretching the limits of my template knowledge and would love to learn why this does not work as I would think it does.

#include <string>
#include <vector>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/type_traits/has_left_shift.hpp>

using namespace std;

struct Point {
    int x;
    int y;
    Point(int x, int y) : x(x), y(y) {}
    string getStr() const { return "("+boost::lexical_cast<string>(x)+","+boost::lexical_cast<string>(y)+")"; }
};

ostream& operator<<(ostream& stream, const Point& p)
{
    stream << p.getStr();
    return stream;
}

template <typename T>
ostream& operator<<(ostream& stream, const std::vector<T>& v)
{
    stream << "[";
    for(auto it = v.begin(); it != v.end(); ++it)
    {
        if(it != v.begin())
            stream << ", ";
        stream << *it;
    }
    stream << "]";
    return stream;
}

template <typename T>
void print(const string& name, T& t)
{
    cout << name << " has left shift = " << boost::has_left_shift<ostream , T>::value << endl;
    cout << "t = " << t << endl << endl;
}

int main()
{
    cout << boolalpha;

    int i = 1;
    print("int", i);

    string s = "asdf";
    print("std::string", s);

    Point p(2,3);
    print("Point", p);

    vector<int> vi({1, 2, 3});
    print("std::vector<int>", vi);

    vector<string> vs({"x", "y", "z"});
    print("std::vector<std::string>", vs);

    vector<Point> vp({Point(1,2), Point(3,4), Point(5,6)});
    print("std::vector<Point>", vp);
}
Jeffrey Grant
  • 113
  • 1
  • 6
  • 2
    Your question is missing, well, a question ;). – Zeta Jan 28 '13 at 19:38
  • Yes, technically there is no question, but in the first paragraph I explain that the boost::has_left_shift trait returns false for STL containers of POD or std::string types (when a valid operator<< overload exists). The implicit question is what is the correct technique for identifying these types. I apologize if that was ambiguous. – Jeffrey Grant Jan 28 '13 at 19:50
  • @JeffreyGrant: I added a workaround to my original answer. You might want to check if that works for you. – Andy Prowl Jan 28 '13 at 21:32

3 Answers3

7

The reason why it does not work is that C++ has sometimes surprising (but well motivated) rules for resolving a function call. In particular, name lookup is first performed in namespace where the call happens and in the namespace of the arguments (for UDTs): if a function with a matching name (or if a matching built-in operator) is found, it is selected (or if more than one are found, overload resolution is performed).

Only if no function with the matching name is found in the namespace of the arguments, the parent namespaces are checked. If a function with a matching name is found is but not viable for resolving the call, or if the call is ambiguous, the compiler will not keep looking up in parent namespaces with the hope of finding a better or unambiguous match: rather, it will conclude that there is no way to resolve the call.

This mechanism is nicely explained in this presentation by Stephan T. Lavavej and in this old article by Herb Sutter.

In your case, the function which checks for the existence of this operator is in the boost namespace. Your arguments are either from the std namespace (ostream, string, vector) or are PODs (int). In the std namespace, a non-viable overload of operator << exist, so the compiler does not bother looking up in the parent (global) namespace, where your overload is defined. It will simply conclude that the (simulated) call done in the boost namespace to check whether operator << is defined can't be resolved.

Now boost::has_left_shift is likely to have some SFINAE machinery for turning what would otherwise be a compilation error into a failed substitution, and will assign false to the value static variable.

UPDATE:

The original part of the answer explained why this does not work. Let's now see if there is a way to work around it. Since ADL is used and the std namespace contains a non-viable overload of operator <<, de facto blocking the attempt to resolve the call, one could be tempted to move the viable overloads of operator << from the global namespace into the std namespace.

Alas, extending the std namespace (and this is what we would do if we were to add new overloads of operator <<) is forbidden by the C++ Standard. What is allowed instead is to specialize a template function defined in the std namespace (unless stated otherwise); however, this doesn't help us here, because there is no template whose parameters can be specialized by vector<int>. Moreover, function templates cannot be partially specialized, which would make things much more unwieldy.

There is one last possibility though: add the overload to the namespace where the call resolution occurs. This is somewhere inside the machinery of Boost.TypeTraits. In particular, what we are interested in is the name of the namespace in which the call is made.

In the current version of the library, that happens to be boost::detail::has_left_shift_impl, but I am not sure how portable this is across different Boost versions.

However, if you really need a workaround, you can declare your operators in that namespace:

namespace boost 
{ 
    namespace detail 
    { 
        namespace has_left_shift_impl
        {
            ostream& operator<<(ostream& stream, const Point& p)
            {
                stream << p.getStr();
                return stream;
            }

            template <typename T>
            std::ostream& operator<<(std::ostream& stream, const std::vector<T>& v)
            {
                stream << "[";
                for(auto it = v.begin(); it != v.end(); ++it)
                {
                    if(it != v.begin())
                        stream << ", ";
                    stream << *it;
                }
                stream << "]";
                return stream;
            }
        } 
    } 
}

And things will start working.

There is one caveat though: while this compiles and runs fine with GCC 4.7.2 with the expected output. However, Clang 3.2 seems to require the overloads to be defined in the boost::details::has_left_shift_impl before the has_left_shift.hpp header is included. I believe this is a bug.

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Andy, thanks for the thorough answer. It was very cogent. If I understand correctly, I could define my own trait for left shift and define the operator<< function within the same namespace to accomplish the same goal. Is this correct? – Jeffrey Grant Jan 29 '13 at 03:55
  • `boost::has_left_shift` defines a `no_operator operator<<(any, any)` overload that is used as a last resort to avoid a compilation error. No SFINAE though. – Maxim Egorushkin Jan 29 '13 at 08:53
3

Your problem has to do with namespaces and name lookup. When you use

boost::has_left_shift<ostream , T>::value

the code resides in namespace boost. It will search there, and in the namespace of the template parameters, but cannot find a matching operator<<. The compiler doesn't look in the global namespace, because none of the involved types reside there.

On the other hand, the print function itself is in the global namespace and will see the operator declared just above it.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Bo, thanks for taking the time to answer my question. Andy's was more detailed and helped me grasp what was going on, but your response is more concise and probably be more helpful to others who already have a grasp on the name lookup process you describe. – Jeffrey Grant Jan 29 '13 at 04:06
  • Pedantically speaking, the fact that `boost::has_left_shift()` is in namespace `boost` does not mean it searches there. It can forward the call to an implementation in another namespace. – Maxim Egorushkin Jan 29 '13 at 09:02
  • @Maxim - Well, yes. My explanation is for the case where the code doesn't specify a namespace (because it doesn't know which one), but uses ADL to locate the operator. – Bo Persson Jan 29 '13 at 10:37
1

When it calls boost::has_left_shift<>() that function considers operator overloads found using argument dependent name lookup. Your overloads are global, but their arguments are from std namespace, this is why argument dependent name lookup in boost::has_left_shift<>() doesn't find them.

To fix you move the overloads into std namespace:

namespace std {

ostream& operator<<(ostream& stream, const Point& p); // definition omitted.

template <typename T>
ostream& operator<<(ostream& stream, const std::vector<T>& v); // definition omitted.

}

As the standard forbids defining new overloads in std namespace, there is a way to package custom printers for standard or 3rd-party classes into its own namespace:

#include <vector>
#include <iostream>

namespace not_std {

// Can't directly overload operator<< for standard containers as that may cause ODR violation if
// other translation units overload these as well. Overload operator<< for a wrapper instead.

template<class Sequence>
struct SequenceWrapper
{
    Sequence* c;
    char beg, end;
};

template<class Sequence>
inline SequenceWrapper<Sequence const> as_array(Sequence const& c) {
    return {&c, '[', ']'};
}

template<class Sequence>
std::ostream& operator<<(std::ostream& s, SequenceWrapper<Sequence const> p) {
    s << p.beg;
    bool first = true;
    for(auto const& value : *p.c) {
        if(first)
            first = false;
        else
            s << ',';
        s << value;
    }
    return s << p.end;
}

} // not_std

int main() {
    std::vector<int> v{1,2,3,4};
    std::cout << not_std::as_array(v) << '\n';
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • The diagnosis is correct, but the proposed solution is not. It is illegal to declare/define new overloads in the `::std` namespace. – David Rodríguez - dribeas Jan 28 '13 at 20:39
  • @DavidRodríguez-dribeas Of course. Forgot to mention this caveat. – Maxim Egorushkin Jan 28 '13 at 20:47
  • Maxim or David, what (if any) is the correct method of overloading operators on types from the `::std` namespace? – Jeffrey Grant Jan 29 '13 at 04:16
  • BTW David, I've been (slowly) reading your blog for a while and hope you'll continue with it in the future. It's been very informative so far. – Jeffrey Grant Jan 29 '13 at 04:18
  • @JeffreyGrant I added a method for you in the answer. – Maxim Egorushkin Jan 29 '13 at 09:00
  • Thanks Maxim. That looks like a good solution. I'm not entirely sure it will work in my context. I'm actually trying to specialize a template which encapsulates boost::any and adds numeric conversion and serialization support. The rub is that following the boost::any model retrieving values requires specifying the stored types exactly, and shimming the SequenceWrapper in may break expectations. But it gives me a good starting point to start hacking. – Jeffrey Grant Jan 29 '13 at 15:33
  • @JeffreyGrant: Shim is a temporary wrapper only to redirect ADL into your own namespace. It is not supposed to be stored anywhere. – Maxim Egorushkin Jan 29 '13 at 15:42
  • @JeffreyGrant you may also like to take a look at `boost::variant` to replace `boost::any` if possible. Also, there is [`dynamic_any<>`](http://cpp-experiment.sourceforge.net/boost/libs/dynamic_any/doc/tutorial.html) that adds necessary operations to `any` for you. – Maxim Egorushkin Jan 29 '13 at 15:44