Output operator and ADL

Josuttis states the following about defining an O/P operator for a type defined in the std namespace ["The C++ Standard Library", 2nd Ed, pg 812] :


Note that a user-defined overload of the output operator << for types defined in the std namespace will have limitations.
The reason is that the overload won’t be found in situations involving ADL.
For instance, this is the case when ostream iterators are used.


Indeed, the following program (based on code in Josuttis' book) confirms this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>         /// cout
#include <ostream>          /// ostream

#include <utility>          /// pair
#include <vector>
#include <algorithm>        /// copy
#include <iterator>         /// ostream_iterator

using namespace std;


template<typename T1,
         typename T2>
ostream& operator<<(ostream& s,
                    const pair<T1, T2>& p)
{
    return s <<
           "[" << p.first << ", " <<
           p.second << "]";
}


int main()
{
    pair<int, long> p {42, 77777};
    cout << "p = " << p << endl;

    vector<pair<int, long>> v
        {
            {10, 10.1}, {20, 20.2}, {30, 3}
        };

    /// error: won't compile
    copy(v.begin(), v.end(),
         ostream_iterator<pair<int, long>> (cout, " "));
}



The following compilation errors occur:

error: cannot bind 'std::ostream_iterator<std::pair<int, long int>>::ostream_type {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'|

error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = std::pair<int, long int>]'|



I have the following queries:

1) Why is ADL required here?

By virture of ADL, if a function isn’t found in the context of its use, it is sought in the namespaces of its arguments.

In this case, the << operator function _is_ found in the context of its use. Therefore, why should ADL come into the picture?

2) Is there a solution?

Thanks.
Last edited on
I have given some more thought to this, and here's my explanation of why ADL comes into the picture here:

1) Here, the ostream_iterator<> function accepts a pair<> as argument. Both are defined in the std namespace.

2) The ostream_iterator<> function is invoked by the copy() algorithm, which is also defined in the std namespace. Thus, the context of use of the ostream_iterator<> function is the copy() algorithm.

3) The ostream_iterator<> uses the << operator.

4) Therefore, the C++ compiler looks for the << operator first in the context of use of the operator, which is the header file in which the copy() algorithm is defined (viz, <algorithm>).

5) Not finding it there, the compiler then uses the ADL lookup rule and looks for the << operator in the namespace of the function argument (pair<>) which is std.

6) Not finding it there either, the compiler issues an error.


As far as a solution is concerned, is it possible to define a function in the std namespace? I don't think so. I have tried it and it doesn't work.
declare the pair through a struct and it's operator overload in a separate namespace:
https://stackoverflow.com/questions/3891402/operator-overloading-and-namespaces
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>         /// cout
#include <ostream>          /// ostream

#include <utility>          /// pair
#include <vector>
#include <algorithm>        /// copy
#include <iterator>         /// ostream_iterator

using namespace std;

namespace detail
{
    template <typename T1, typename T2>
    struct PairClass
    {
        std::pair<T1, T2> m_classPair;
        PairClass (const T1 first, const T2 second)
        {
            m_classPair = std::make_pair(first, second);
        }

    };
    template <typename T1, typename T2>
    std::ostream& operator << (ostream& os, const PairClass<T1, T2>& p)
    {
        return os << "[" << p.m_classPair.first << ", " << p.m_classPair.second << "]";
    }
}

int main()
{
    detail::PairClass<int, double> p {42, 77777};
    std::cout << "p = " << p << std::endl;

    std::vector<detail::PairClass<int, double>> v
        {
            {10, 10.2}, {20, 20.3}, {30, 3.4}
        };

    std::copy(v.begin(), v.end(),
         std::ostream_iterator<detail::PairClass<int, double>> (std::cout, " "));
}

http://coliru.stacked-crooked.com/a/3ea168c49115300b
btw if you're going to be using decimals then to avoid narrowing errors use double as the second template parameter in your example, instead of long
edit: OP: your second post and mine crossed in which you've going some way in explaining your initial queries
Last edited on
Can this be of any help?
http://en.cppreference.com/w/cpp/language/adl
Name lookup rules make it impractical to declare operators in global or user-defined namespace that operate on types from the std namespace, e.g. a custom operator>> or operator+ for std::vector or for std::pair (unless the element types of the vector/pair are user-defined types, which would add their namespace to ADL). Such operators would not be looked up from template instantiations, such as the standard library algorithms. See dependent names for further details.

> is it possible to define a function in the std namespace? I don't think so.

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified. A program may add a template specialization for any standard library template to namespace std only if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.
http://eel.is/c++draft/namespace.std


> Is there a solution?

Yes; one without messing around with the namespace std.

a. Define a wrapper which can wrap any object of any type implicitly (one with a converting constructor)

b. Define overloaded stream insertion operators for wrappers holding objects of interest
(in the same namespace where the wrapper is defined; so that it would be found via ADL).

c. Write the standard library algorithms in terms of stream iterators which deal with wrapped objects
(the implicit conversion would take care of sending a wrapped object to the stream).

d. As is usual in these cases, a little bit of boilerplate can provide syntactic sugar.

Something along these lines, perhaps:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <set>

namespace utility
{
    // note: converting constructor, implicit conversion from T to wrapper<T>
    template < typename T > struct wrapper { const T& v ; wrapper( const T& v ) noexcept : v(v) {} };

    template< typename T1, typename T2 > // overload for wrapper<> will be found via ADL
    std::ostream& operator<< ( std::ostream& stm, const wrapper< std::pair<T1,T2> >& h )
    { return stm << "[" << h.v.first << ", " << h.v.second << "]"; }

    template< typename T, typename A > // overload for wrapper<> will be found via ADL
    std::ostream& operator<< ( std::ostream& stm, const wrapper< std::vector<T,A> >& h )
    {
        stm << "[ " ;
        for( const auto& value : h.v ) stm << value << ' ' ;
        return stm << "] (size:" << h.v.size() << ')';
    }

    template < typename T >
    using wrapping_ostream_iterator = std::ostream_iterator< utility::wrapper<T> > ;
}


template < typename SEQ >
auto make_ostream_iterator( const SEQ&, std::ostream& stm, const char* cstr )
{ return utility::wrapping_ostream_iterator< typename SEQ::value_type >{ stm, cstr } ; }

int main()
{
    const std::vector< std::pair<int,double> > vec { {10, 10.1}, {20, 20.2}, {30, 3} };

    std::copy( vec.begin(), vec.end(), make_ostream_iterator( vec, std::cout, " " ) ) ;
    std::cout << "\n\n" ;

    const std::set< std::vector<int> > set { {0,1,2}, {3,4}, {5,6,7,8}, {}, {9,10,11} } ;
    std::copy( set.begin(), set.end(), make_ostream_iterator( set, std::cout, "\n" ) ) ;
}

http://coliru.stacked-crooked.com/a/fd2a3b4c43a1cf9f
http://rextester.com/SAEK82934
Gunner, a marvelous and simple solution. Thanks.
OP: despite your kind words (thanks for them anyways) I'd probably go with JLBorges' more elegant solution. Notice how in his/her program namespace utility is not mentioned at all within main() (unlike my efforts). This separates the interface of and implementation for the user-defined namespace (so to speak), this being done through the template function std::make_ostream_iterator()
Last edited on
OK. I was merely alluding to the fact that gunner's solution was simple.
Topic archived. No new replies allowed.