LINUX.ORG.RU

Перегрузка оператора <<

 ,


1

5

Есть класс S с шаблонным оператором <<. Вызывается он из другой шаблонной функции run(), которая инстанциируется в main(). При этом есть перегрузка оператора << с типом MyValue, объявленная до самого main(). То-есть в момент инстанциации (тьфу что за слово такое) шаблона run() видны дефолтный метод (S::operator<<()) и перегруженный и компилятор должен выбрать перегруженный. GCC так и делает, но Clang всё равно игнорирует перегрузку и вызывает дефолтный метод.

При этом если объявить перегруженный метод до объявления шаблона run() (путём простого перемещения #include «run.h» пониже), то Clang начинает работать как и положено, то-есть вызывать перегруженный метод.

Кто прав, кто виноват, и как сие чинить, чтобы был порядок и все компиляторы были счастливы?

run.h

#include <stdio.h>

struct S {
    template <typename T>
    void operator<<(T) { printf("DEFAULT\n"); }
};

namespace N {
template <typename T>
void run(const T & value) { S s; s << value; }
}

main.cpp (работает в GCC, не работает в clang)

#include "run.h"

struct MyValue {};

namespace N {
void operator<<(S&, MyValue) { printf("OVERLOADED\n"); }
}

int main(int, char**)
{
	N::run(MyValue());
	return 0;
}

main.cpp (работает и там, и там, #include «run.h» передвинут под перегрузку оператора <<)

#include <stdio.h>

struct S;
struct MyValue {};

namespace N {
void operator<<(S&, MyValue) { printf("OVERLOADED\n"); }
}

#include "run.h"

int main(int, char**)
{
	N::run(MyValue());
	return 0;
}

★★★★★

инстанциации (тьфу что за слово такое)

инстанциирования

Может как-то так будет работать везде?

namespace N {
template <typename T, typename O = S>
void run(const T & value) { O s; s << value; }
}

clang почему-то разрешает operator<< при разборе run, а не отложено.

Стандарт говорит:

[temp.dep]
[...] denotes a dependent name if:
[...]
any of the expressions or braced-init-lists in the expression-list is
type-dependent,
[...]
If an operand of an operator is a type-dependent expression, the operator also
denotes a dependent name.  Such names are unbound and are looked up at the point
of the template instantiation ([temp.point]) in both the context of the template
definition and the context of the point of instantiation.

Т.е. s << value;, вроде, тоже dependent и должно работать как в GCC.

xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

Может как-то так будет работать везде?

Не помогло. Впрочем, я уже приблизился к разгадке: перегрузка должна быть в том же пространстве имён, что и класс S.

Но тут возникает другая проблем. В действительности класс S это std::ostream, но изначально он объявлен в другом namespace, конкретно у Clang это std::__1::ostream, где __1 есть inline namespace. То-есть всё что внутри __1, в частности ostream, видно в std. Тем не менее попытка перегрузить оператор через:

namespace std {
ostream& operator<<(ostream&, MyType);
}

проблему не решает. Только хардкор:

namespace std { inline namespace __1 {
ostream& operator<<(ostream&, MyType);
}}

Так работает и перегрузка подхватывается. Теперь возвращаемся к нашим баранам. Как таки написать так, чтобы было портабельно и меня за такой код не прокляли.

Dendy ★★★★★ ()

Ну ты может чего со стандартами C++ наперлолил.

Во флагах компилятора есть указание что использовать. Может там так, там сяк.

fornlr ★★★★★ ()
Ответ на: комментарий от xaizek

А MyValue и перегрузку в одном пространстве имён нельзя определить?

Таки перепроверил ещё раз, оказывается перегрузка работает в том же пространстве имён, даже если не указывать тот же inline namespace.

Dendy ★★★★★ ()

Все 4 стандарта (03, 11, 14, 17) солидарны в том, что

Dependent name resolution [temp.dep.res]

In resolving dependent names, names from the following sources are considered:

  • Declarations that are visible at the point of definition of the template.
  • Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.

OVERLOADED не виден at the point of definition of the template, и не находится через ADL. Так что clang прав.

utf8nowhere ★★ ()
Ответ на: комментарий от utf8nowhere

Напишите мне заголовок, текст самого багрепорта на английском и минимальный пример кода - вброшу. Самому мне лень вникать в эти перегрузки и темплейты, я на плюсах не пишу.

SZT ★★★ ()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

dependent name resolution selects a function it should have NEVER considered

[temp.dep.res]/1: In resolving dependent names, names from the following sources are considered:
— Declarations that are visible at the point of definition of the template.
— Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.

In the code below, g++ selects the free operator<<(), even though it is declared after the definition of the template and can't be found using ADL.

#include <cstdio>

struct S {
    template <typename T>
    void operator<<(T) { std::printf("DEFAULT\n"); }
};

namespace N {
    template <typename T>
    void run(const T & value) { S s; s << value; }
}

struct MyValue {};

namespace N {
    void operator<<(S&, MyValue) { std::printf("OVERLOADED\n"); }
}

int main(int, char**)
{
    N::run(MyValue());
}

Expected output: DEFAULT
Actual output: OVERLOADED

utf8nowhere ★★ ()
Последнее исправление: utf8nowhere (всего исправлений: 1)
Ответ на: комментарий от SZT

Я видел.

Ты предлагаешь отвечать через тебя? Ок.

Strictly speaking, it is not a dup, because PR 51577 and all the bugs marked as dups of PR 51577 are about finding an operator with namespaced parameters in the global namespace, but here the situation is the reverse. But the bugs could be closely related.

utf8nowhere ★★ ()