<< оператор переопределения компилируется с g ++, а не с windows - PullRequest
0 голосов
/ 03 мая 2018

Я пытаюсь перенести приложение в окна (ирония, я знаю). Следующий простой пример иллюстрирует проблему. Я получаю следующую ошибку при компиляции с VS12 и 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)

Нет ошибок в Ubuntu с g ++. Чего мне не хватает?

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;
}

команды и флаги

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" 

1 Ответ

0 голосов
/ 03 мая 2018

Краткий ответ: Это ошибка компилятора в MSVC 2015 и старше; Чтобы обойти это, напишите using ::operator<<; непосредственно перед строкой с сообщением об ошибке.


Эта проблема касается поиска имен в определениях встроенных друзей. Вот упрощенный пример проблемы:

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)
    };
}

Как правило, поиск по неквалифицированному имени будет делать две вещи:

  • Ищите имя в текущей области. Если не найдено, посмотрите в родительской области и т. Д., Включая глобальное пространство имен. Остановитесь, когда мы найдем имя . То есть, если имя находится в текущей области, а также в родительской области, тогда имя в родительской области не будет найдено.
  • ADL, т. Е. Также поиск в пространствах имен любых аргументов вызова функции.

Обратите внимание, что в этом коде f(R::S) не объявлено в пространстве имен R, поэтому ADL никогда не найдет его. Его можно найти только по первой части безошибочного поиска.

Таким образом, существует потенциальная проблема, заключающаяся в том, что любое имя f, встречающееся внутри namespace N, может скрывать глобальное f. Это можно увидеть в действии, если вы удалите строку friend и поместите void f(E e) { f(e.var); } как функцию в N (не в E). Тогда поиск имени находит N::f и прекращает поиск, не найдя ::f.

Теперь, поиск имени friend функции , впервые определенной внутри класса, немного необычен. Цитирование из cppreference :

Имя, впервые объявленное в объявлении друга в классе или шаблоне класса X, становится членом внутреннего вложенного пространства имен X, но не отображается для поиска (кроме поиска, зависящего от аргумента, который учитывает X), если только не найдено соответствующее объявление в предоставляется пространство имен.

Это означает, что при вызове f(e.var) функция N::f фактически не видна для поиска. Таким образом, поиск должен продолжаться до тех пор, пока мы не найдем ::f, и добьемся успеха.

MSVC 2015, похоже, знает об этом правиле поиска друзей, поскольку оно корректно отклоняет struct A { friend void a() { a(); } };, однако затем не удается продолжить поиск внешних областей для другого объявления имени.

Объявление using ::operator<<; означает, что при поиске N будет найдено ::operator<<; видя, что MSVC 2015 принимает это, по-видимому, он все еще ищет N, но просто не повторяется вверх, если поиск не удался.


Комментарий: Эта проблема с тенями имен функций всегда является проблемой, когда у вас есть operator<<, который не найден ADL. Даже в правильном компиляторе вы можете обнаружить, что ваш код раздражающе перестает работать, когда у вас есть какие-то несвязанные operator<< помехи. Например, если вы определите operator<< в namespace Logging, то даже в g ++ и MSVC 2017 код перестанет работать.

В этом случае работает тот же using ::operator<< обходной путь, но раздражает необходимость продолжать это делать. Вы действительно должны сделать using ::operator<< внутри любого пространства имен N, которое объявляет свои собственные operator<< любого вида, что все еще раздражает, но немного меньше. Для этой цели может быть предпочтительнее использовать функцию с несколько уникальным именем вместо operator<<.

Обратите внимание, что ADL не может найти operator<<(std::ostream, std::chrono...), поскольку все аргументы находятся в std, а ::operator<< - в std. Неопределенное поведение - добавлять свои собственные бесплатные функции в namespace std, поэтому вы не можете обойти это и так.

...