user2394284
user2394284

Reputation: 6018

Can I namespace the ostream << operator?

As has been answered before, namespacing operator overloads is considered good practice, so that's what I want to do.

Problem: It only compiles if I don't. Am I just doing it wrong, or have I found an exception where it is not possible?

Here is a single translation unit for easy repro:

// lib/halfseconds.h:
#include <chrono>

namespace lib {
    using halfseconds = std::chrono::duration<intmax_t, std::ratio<1, 2> >;
}

// lib/debug.h:
#include <ostream>

namespace lib {
    std::ostream& operator<<(std::ostream& o, lib::halfseconds halves)
    {
        double seconds = halves.count();
        seconds /= lib::halfseconds::period::den;
        o << seconds << 's';
        return o;
    }
}

// demo/main.cpp:
#include <iostream>

int main()
{
    lib::halfseconds threeHalvseconds(3);
    std::cout << threeHalvseconds << '\n'; // 1.5s
}

What does the compiler say? G++ version 8.2.1 says «no match for operator<<» and spews one daunting list (208 lines) of candidates. I suppose none of those are relevant, as I wouldn't get this error if the relevant one wasn't missing.

Upvotes: 0

Views: 220

Answers (2)

Alecto
Alecto

Reputation: 10740

The principle you're trying to use is called Argument Dependent Lookup. If I have a function and a type declared in the same namespace, I can use them together outside of the namespace without having to specify which namespace the function is from:

//In MyClass.h
namespace foo {
    class MyClass { /* stuff */ }; 
}
//In doStuff.h
namespace foo {
    void doStuff(MyClass c) { /* stuff */ }
}

//in main.cc
int main() {
    foo::MyClass tom; //I'm bad with names
    doStuff(tom); //Here, we don't have to specify the namespace
}

This is almost what's happening in your example. The difference is that halfseconds isn't actually declared in namespace lib. Because halfseconds is an alias, it's actually declared in std::chrono, and when you put the operator<< overload in namespace lib, the compiler doesn't check for it.

How to fix this

The simplest way to fix this is to declare a new type in namespace lib:

// lib/halfseconds.h:
#include <chrono>

namespace lib {
    class halfseconds 
        : public std::chrono::duration<intmax_t, std::ratio<1, 2>> 
    {
       public:
        using Base = std::chrono::duration<intmax_t, std::ratio<1, 2>>;
        using Base::Base; //Use the constructor
    };
}

// lib/debug.h:
#include <ostream>

namespace lib {
    std::ostream& operator<<(std::ostream& o, lib::halfseconds halves)
    {
        double seconds = halves.count();
        seconds /= lib::halfseconds::period::den;
        o << seconds << 's';
        return o;
    }
}

// demo/main.cpp:
#include <iostream>

int main()
{
    lib::halfseconds threeHalvseconds(3);
    std::cout << threeHalvseconds << '\n'; // 1.5s
}

You can use it anywhere you'd use std::chrono::duration, it has all the same functionality, and because it's defined in the lib namespace it can be used with any other functions in the lib namespace without having to prefix lib!

Upvotes: 3

Carlo Piovesan
Carlo Piovesan

Reputation: 332

The problem here is that once you have put operator<< in a namespace, you should explicitly tell the compiler you would like to use the namespace, otherwise it's hidden.

Either:

  • using lib::operator<< inside the main
  • lib::operator<<(std::cout, threeHalvseconds) << '\n';

Upvotes: 3

Related Questions