0

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.

user2394284
  • 5,520
  • 4
  • 32
  • 38

2 Answers2

3

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';
  • 2
    Upvoted, but here is additional information: When this code gets upgraded to C++20, the first bullet will cause a conflict because C++20 adds a streaming operator for `lib::halfseconds` (which is a type alias for `std::chrono::duration`). The fix will be to remove `lib::operator<<`, though the syntax of the output will then change to `3[1/2]s`. – Howard Hinnant Dec 14 '18 at 22:04
3

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!

Community
  • 1
  • 1
Alecto Irene Perez
  • 10,321
  • 23
  • 46