Неоднозначный вызов шаблонной функции из-за 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
  • те, которые не являются, и что вы должны явно квалифицироваться, чтобы избежать проблем этого типа

если вы выпадете из этих правил, это может сработать или нет, в зависимости от того, что включено, и это очень неудобно.