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