Неоднозначный вызов шаблонной функции из-за ADL
Я был укушен этой проблемой пару раз, и мои коллеги тоже. При компиляции
#include <deque>
#include <boost/algorithm/string/find.hpp>
#include <boost/operators.hpp>
template< class Rng, class T >
typename boost::range_iterator<Rng>::type find( Rng& rng, T const& t ) {
return std::find( boost::begin(rng), boost::end(rng), t );
}
struct STest {
bool operator==(STest const& test) const { return true; }
};
struct STest2 : boost::equality_comparable<STest2> {
bool operator==(STest2 const& test) const { return true; }
};
void main() {
std::deque<STest> deq;
find( deq, STest() ); // works
find( deq, STest2() ); // C2668: 'find' : ambiguous call to overloaded function
}
...компилятор VS9 терпит неудачу при компиляции второй находки. Это связано с тем, что STest2
наследуется от типа, определенного в пространстве имен boost, который запускает компилятор, чтобы попробовать ADL, который находит boost::algorithm::find(RangeT& Input, const FinderT& Finder)
.
очевидным решением является префикс вызова find(…)
С "
4 ответов
ADL не является резервным механизмом для использования, когда" нормальное " разрешение перегрузки терпит неудачу, функции, найденные ADL, так же жизнеспособны, как функции, найденные обычным поиском.
Если ADL был резервным решением, вы могли бы легко попасть в ловушку, если бы функция использовалась даже тогда, когда была другая функция, которая была лучше, но только видимая через ADL. Это казалось бы особенно странным в случае (например) перегрузок оператора. Вы не хотите сравнить два объекта через operator==
для типов, которые они могут быть неявно преобразованы, когда существует совершенно хороший operator==
в соответствующем пространстве имен.
Я добавлю очевидный ответ сам, потому что я только что сделал некоторые исследования по этой проблеме:
C++03 3.4.2
§2 для каждого типа аргумента T в вызове функции существует набор нулевых или более связанных пространств имен [...] Наборы пространств имен и классов определяются следующим образом:
[...]
- Если T-тип класса (включая объединения), его связанные классы: сам класс; класс, из которого он является ля член, если таковой имеется;и его прямые и косвенные базовые классы. Связанные пространства имен пространства имен в которой определяются связанные классы.
§ 2a если обычный неквалифицированный поиск имени находит объявление функции-члена класса, связанные с пространства имен и классы не считаются. В противном случае набор объявлений, найденных при поиске имя функции-объединение найденного набора объявлений использование обычного неквалифицированного поиска и набора объявления, найденные в пространствах имен и классах, связанных с типами аргументов.
по крайней мере, это соответствует стандарту, но я все еще не понимаю обоснования здесь.
рассмотрим mystream
, который наследует от std::ostream
. Вы хотели бы, чтобы ваш тип поддерживал все <<
операторы, определенные для std::ostream
обычно в пространстве имен std. Таким образом, базовые классы являются связанными классами для ADL.
Я думаю, что это также вытекает из принципа замещения и функции в пространство имен класса являются частью его интерфейса (см. герб Саттер: "а что в классе?"). Таким образом, интерфейс, работающий на базовом классе, должен оставаться работа над производным классом.
вы также можете обойти это, отключив ADL:
(find)( deq, STest2() );
Я думаю, вы сами заявили о проблеме:
в глобальном пространстве имен
функции в глобальном пространстве имен считаются последними. Это самая внешняя область по определению. Любая функция с тем же именем (не обязательно применимая), которая находится в более близкой области (с точки зрения вызова), будет подобрана первой.
template <typename Rng, typename T>
typename Rng::iterator find( Rng& rng, T const& t );
namespace foo
{
bool find(std::vector<int> const& v, int);
void method()
{
std::deque<std::string> deque;
auto it = find(deque, "bar");
}
}
здесь (если vector
или deque
включить algorithm
, что разрешено), единственное метод, который будет подобран во время поиска имени, будет:
bool foo::find(std::vector<int> const&, int);
если algorithm
как-то включен, также будет:
template <typename FwdIt>
FwdIt std::find(FwdIt begin, FwdIt end,
typename std::iterator_traits<FwdIt>::value_type const& value);
и конечно, разрешение перегрузки не удастся, заявив, что нет совпадений.
обратите внимание, что поиск имени чрезвычайно глуп: ни arity, ни тип аргумента не рассматриваются!
таким образом, есть только два вида свободных функций, которые вы должны использовать в C++:
- те, которые являются частью интерфейса класса, объявленного в том же пространстве имен, подобранного ADL
- те, которые не являются, и что вы должны явно квалифицироваться, чтобы избежать проблем этого типа
если вы выпадете из этих правил, это может сработать или нет, в зависимости от того, что включено, и это очень неудобно.