Namespace issues in C++ when friend function is defined outside class body

Why operator<< for Test struct do not work when defined outside struct body?

#include <chrono>
#include <ostream>
#include <iostream>

namespace my::time
{

std::ostream& operator<<(std::ostream& stream, const std::chrono::system_clock::time_point& time)
{
    stream << "...";
    return stream;
}

} // namespace my::time

namespace other_namespace::nested
{

struct Test
{
    std::chrono::system_clock::time_point _timePoint;

    friend std::ostream& operator<<(std::ostream& os, const Test& t1);
};

std::ostream& operator<<(std::ostream& os, const Test& t1)
{
    using namespace my::time;
    os << t1._timePoint; // <------ here is problem
     return os;

}

}


int main()
{
    other_namespace::nested::Test t;
    t._timePoint = std::chrono::system_clock::now();
    std::cout << t << std::endl;
    return 0;
}

Godbolt link to example

Issue:

Invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const std::chrono::system_clock::time_point' (aka 'const time_point<std::chrono::system_clock, duration<long, ratio<1, 1000000000>>>'))

But there is no issue when I define same operator like this:

struct Test
{
    std::chrono::system_clock::time_point _timePoint;

    friend std::ostream& operator<<(std::ostream& os, const Test& t1)
    {
        using namespace my::time;
        os << t1._timePoint;
        return os;
    }
};

PS. I know that in newer C++ stdlib there is implementation for such operator<< but I would like to use my own.

Build using: clang 16 with -std=c++20
libstdc++ version: 20220924, _GLIBCXX_RELEASE:12
Using Alpine Linux 18.03

PS2. When using newer compiler it will compile but it will use std::chrono implementation not mine. So example output would be:

std::cout << t << std::endl; // 2023-10-14 08:18:53.841272299

Instead: “…”

  • 2

    @Jason why you waste time if you don’t want read and understand my question. Issue here is that in older cimpiler/stdlib there is no std::chrono::operator<< implemented. But my intention is to use my own operator<< for time_point. In newer version like clang17 it will compile but return bad result.

    – 

This is because a friend function defined in a class is not found by a normal unqualified lookup, including from within the function itself. Whereas a function defined outside of a class is found, and that stops any further lookup.

Here is a minimalistic demo:

struct Foo
{
};

namespace FooImpl
{
    void doit(Foo) {}
}

namespace BarImpl
{
    struct Bar
    {
        friend void doit(Bar);
    };

    void doit(Bar) { 
        using namespace FooImpl;
        doit(Foo{});
    }
}

The error message is straightforward:

moo.cpp: In function ‘void BarImpl::doit(Bar)’:
moo.cpp:21:14: error: could not convert ‘Foo()’ from ‘Foo’ to ‘BarImpl::Bar’

So you can see that the unqualified lookup for doit finds BarImpl::doit and stops. FooImpl is not even considered because unqualified lookup doesn’t propagate outwards once a name is found.

Rename BarImpl::doit(Bar) to BarImpl::dothat(Bar), and the lookup suddenly finds FooImpl::doit, like magic.

One workaround to achieve what you want(which is to provide the definition of the friend function outside the struct) is to use using declaration instead of using directive as shown below.

Note the use of using my::time::operator<< instead of using namespace my::time.

struct Test
{
    //other code as before
    friend std::ostream& operator<<(std::ostream& os, const Test& t1);
};

std::ostream& operator<<(std::ostream& os, const Test& t1)
{
    using my::time::operator<<;  //changed this to using declaration instead of using directive
    os << t1._timePoint; //      works now
    
    return os;
}

} 

working demo

Leave a Comment