Argument-dependent lookup в C++
Как-то со всеми этими новыми модными штучками типа концептов и constexpr
стали забываться базовые вещи из C++ и вся та жесть, которая может быть с ними связана. Вот сейчас нарвался на баг, связанный с со старым добрым Argument-dependent lookup, и задумался о том, а как вообще писать безопасный с точки зрения ADL код в библиотеках.
Допустим вы реализуете какую-то библиотеку lib1
, в которой содержатся шаблонные функции для работы с произвольными определёнными пользователем типами данных. Среди них в реализации есть какая-нибудь мелкая функция, например calc_size
:
namespace lib1 {
template <typename T>
int calc_size(const T & v) {
// calculating and returning size using methods of v
}
template <typename T>
void my_public_func(const T & v) {
// ...
int sz = calc_size(v);
// ...
}
}
Казалось бы, всё находится в отдельном пространстве имен lib1
, и никаких проблем быть не должно. Но не тут то было. Теперь представим, что у вас есть библиотека lib2
, в которой определён класс cls
, и в заголовочном файле в реализации библиотеки есть вспомогательная функция с тем же самым именем:
namespace lib2 {
class cls {
// ...
};
inline int calc_size(const cls & v) {
// ...
}
}
Теперь если передать в функцию lib1::my_public_func
параметр типа cls
, то вызовется не lib1::calc_size
, а lib2::calc_size
. Всё, можно идти и отлаживать баг часами. Повезёт, если функция lib2::calc_size
будет тоже шаблонной, тогда компилятор выдаст ошибку о том, что вызов calc_size
неоднозначен.
Проблема решается отправкой всех вспомогательных функций в отдельное вложенное пространство имён. К тому же это прячет все детали реализации так, что у пользователя не будет желания вызывать такиефункции напрямую:
namespace lib2 {
class cls;
namespace impl {
// этот calc_size больше не будет использоваться в ADL для класса cls.
inline int calc_size(const cls & v) {
// ...
}
}
}
А что делать, если функция calc_size
должна быть доступна для пользователей библиотеки lib2
? Можно использовать директиву using namespace impl;
внутри пространства имён lib2
, или вместо этого использовать безымянное пространство имён.
namespace lib2 {
namespace
class cls;
namespace {
// calc_size не будет использоваться в ADL для класса cls
inline int calc_size(const cls & v) {
// ...
}
}
namespace {
void some_other_func(const cls & v) {
calc_size(v); // OK
}
}
}
cls c;
::lib2::calc_size(c); // OK
Вообще получается, что безымянное пространство имён оставляет функции доступными в родительском пространстве имён, но отключает для этих для функций ADL. Поэтому по-хорошему все публичные функции библиотеки должны находиться в безымянном пространстве имён, если тольно не предполагается, что они будут использоваться в ADL.
comments powered by Disqus