Исходный пример:
#include <ostream>
template<typename T>
void serialize(std::ostream& stream, const T& value) {}
template<typename T>
class Field
{
public:
void serialize(std::ostream& stream)
{
serialize(stream, m_value);
}
protected:
T m_value = {};
};
Пример кода не компилируется (gcc-9, std=c++2a). Однако вывод компилятора подсказывает, что функция serialize(std::ostream&, const T&)
недоступна для вызова из данного контекста. Меняем тело метода serialize
на следующий код:
::serialize(stream, m_value);
Все работает. Однако, для меня неочевидно, почему возникают такие проблемы - функции, имеющие разные сигнатуры, внезапно создают коллизии. Дайте ссылку на конкретный параграф стандарта.
P.S. Проверил с non-template классом - ситуация аналогичная.
Сразу надо заметить, что немедленной ошибки в приведенном вами коде нет, ибо приведенный шаблонный код не инстанцирован и name lookup для него сразу не выполняется, ибо внутренний вызов serialize
зависит от шаблонного параметра T
. И при конкретном инстанцировании результаты процесса name lookup для имени serailize
теоретически могут зависеть и от конкретного типа T
(см. ниже), и от того места, в котором выполнено инстанцирование. В вашем примере ничего этого не приведено.
Но с общей точки зрения вы наблюдаете классический name hiding. Любое имя во вложенной области видимости скрывает все имена из охватывающих областей видимости.
В явном виде это явление в стандарте не описано (или может даже описано, как вы сами сказали в [basic.scope.hiding]
), но в любом случае оно вытекает напрямую из того факта, что name lookup и overload resolution - два отдельных этапа в С++. Сначала делается unqualified name lookup, который традиционно выполняет поиск изнутри-наружу снизу-вверх и останавливается на той области видимости, к которой найдено хотя бы одно подходящее имя. Затем запускается процесс overload resolution только для имен, найденных именно в этой области видимости.
Тут можно было бы понадеяться на argument-dependent lookup (ADL), который при правильном типе аргумента m_value
мог бы увидеть и глобальное объявление serialize
. Однако стандарт явно говорит, что если обычный name lookup нашел в качестве кандидата имя члена класса, то ADL тут же игнорируется (http://eel.is/c++draft/basic.lookup.argdep#3.1). Поэтому в вашем случае ADL не помогает.
В нижеприведенном же примере тоже имеет место name hiding, но сокрытие выполняется не методом класса, а внешней функцией
#include <ostream>
#include <iostream>
struct S {};
template<typename T>
void serialize(std::ostream& stream, const T& value) {}
namespace X {
void serialize(std::ostream& stream) {}
template<typename T>
class Field
{
public:
void foo(std::ostream& stream)
{
serialize(stream, m_value);
}
protected:
T m_value = {};
};
}
int main()
{
X::Field<int> f1;
f1.foo(std::cout); // <- ошибка
X::Field<S> f2;
f2.foo(std::cout); // <- все в порядке
}
В таком варианте ADL не игнорируется. В этом примере при T == int
ADL ничего не находит и мы получаем ту же самую ошибку по той же самой причине, что и в вашем примере. А вот при T == S
ADL позволяет нам найти еще и глобальную serialize
и ошибка пропадет.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Есть код простой небольшой демки на C++В ней реализовывается механика "ходить, ставить блоки, убирать блоки"
Проблема заключается в том, что в разных местах программы функции, работающие со QString, ведут себя по-разному в отношении обработки кириллицыЭто...
Как объекту класса kvadrat вызвать методы класса rectangle, а потом опять вызывать методы kvadrat??