atomSmasher
atomSmasher

Reputation: 1465

<< operator override compiles with g++ not windows

I am trying to port an application to win-dows (ironic, I know). The following bare-bone example illustrates the problem. I get the following error when compiling with VS12 and VS14:

C2679 binary '<<': no operator found which takes a right-hand operand
of type 'std::chrono::time_point<std::chrono::system_clock,std::chrono::system_clock::duration>'
(or there is no acceptable conversion)

No errors on Ubuntu with g++. What am I missing?

logger.h

#pragma once
#include "stdafx.h"

#include <chrono>
#include <ostream>

std::ostream& operator<<(std::ostream &out, const std::chrono::time_point<std::chrono::system_clock> &time_point);

namespace Logging
{
    inline std::chrono::time_point<std::chrono::system_clock> timestamp()
    {
        std::chrono::time_point<std::chrono::system_clock> time_point = std::chrono::system_clock::now();
        return time_point;
    }

    class Event
    {
    public:
        Event() : time_point(timestamp())
        {
        }
        typedef std::unique_ptr< Event > Ptr;

        friend std::ostream& operator<<(std::ostream &out, const Ptr &p)
        {
            out << p->time_point << "\t";  // << LINE CAUSING ERROR
            return out;
        }

    private:
        const std::chrono::time_point<std::chrono::system_clock> time_point;
    };
}

logger.cpp

#include "stdafx.h"

#include <memory>
#include <ctime>
#include "logger.h"


std::ostream& operator<<(std::ostream &out, const std::chrono::time_point<std::chrono::system_clock> &time_point)
{
    std::time_t time = std::chrono::system_clock::to_time_t(time_point);
    struct tm t;
    localtime_s(&t, &time);  //localtime(&time) on linux
    char buf[30];
    int ret = ::strftime(buf, 30, "%Y/%m/%d %T", &t);
    out << buf;
    return out;
}

commands and flags

linux:

g++ -std=c++11 -Wall logger.cpp app.cpp -o app

windows:

/Yu"stdafx.h" /GS /analyze- /W3 /Zc:wchar_t /ZI /Gm /Od /sdl /Fd"Debug\vc140.pdb" /Zc:inline /fp:precise /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /RTC1 /Gd /Oy- /MDd /Fa"Debug\" /EHsc /nologo /Fo"Debug\" /Fp"Debug\chron_test.pch" 

Upvotes: 3

Views: 89

Answers (1)

M.M
M.M

Reputation: 141638

Short answer: It is a compiler bug in MSVC 2015 and older; to work around it, write using ::operator<<; just before the line giving the error.


This issue concerns name lookup of inline friend definitions. Here is a simplified example of the problem:

namespace R { struct S {}; }
void f(R::S) {}

namespace N
{
    struct E
    {
        R::S var;
        friend void f(E e) { f(e.var); }   // OK, should find f(R::S)
    };
}

Generally, unqualified name lookup will do two things:

  • Look for the name in the current scope. If not found, look in the parent scope, etc. up to and including the global namespace. Stop when we find the name. That is, if the name is in the current scope and also in a parent scope, then the name in the parent scope would NOT be found.
  • ADL, i.e. also search the namespaces of any arguments to the function call.

Note that in this code f(R::S) is not declared in namespace R, so it is never found by ADL. It can only be found by the first part of unqualfiied lookup.

So there is a potential problem that any name f occurring inside namespace N may hide the global f. You can see this in action if you remove the friend line and put void f(E e) { f(e.var); } as a function in N (not in E). Then the name lookup finds N::f, and stops searching, never finding ::f.

Now, name lookup of a friend function first defined inside a class is a bit unusual. Quoting from cppreference:

A name first declared in a friend declaration within class or class template X becomes a member of the innermost enclosing namespace of X, but is not visible for lookup (except argument-dependent lookup that considers X) unless a matching declaration at the namespace scope is provided.

That means that in the call f(e.var), the function N::f is actually not visible for lookup. So the search should keep going up scopes until we find ::f, and succeed.

MSVC 2015 does seem to know about this friend lookup rule, as it correctly rejects struct A { friend void a() { a(); } }; , however it then fails to continue looking up the outer scopes for another declaration of the name.

The using ::operator<<; declaration means that the search of N will find ::operator<<; seeing as MSVC 2015 accepts this, apparently it is still searching N but just not recursing upwards if that search fails.


Commentary: This problem with function names being shadowed is always a problem when you have operator<< which is not found by ADL. Even in a correct compiler, you may find your code annoyingly stops working when you have some unrelated operator<< interfering. For example if you define an operator<< in namespace Logging then , even in g++ and MSVC 2017, the code will stop working.

The same using ::operator<< workaround works in this case, but it's annoying to have to keep doing that. You really have to do using ::operator<< inside any namespace N that declares its own operator<< of any sort, which is still annoying but a bit less so. It may be preferable to use a function with a somewhat unique name, instead of operator<<, for this purpose.

Note that it is not possible for operator<<(std::ostream, std::chrono...) to be found by ADL, because all of the arguments are in std but ::operator<< is not in std. It is undefined behaviour to add your own free functions to namespace std, so you can't work around it that way either.

Upvotes: 3

Related Questions