Неоднозначный вызов шаблонной функции из-за ADL - PullRequest
7 голосов
/ 09 сентября 2010

Я был укушен этой проблемой пару раз, как и мои коллеги.При компиляции

#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 наследуется от типа, определенного в расширенном пространстве имен, который запускает компилятор для попытки ADL, который находит boost::algorithm::find(RangeT& Input, const FinderT& Finder).

Очевидное решение - префикс вызова к * 1008.* с "::", но зачем это нужно?В глобальном пространстве имен существует абсолютно правильное совпадение, так зачем вызывать Argument-Dependent Lookup?Кто-нибудь может объяснить здесь обоснование?

Ответы [ 4 ]

7 голосов
/ 09 сентября 2010

ADL не является резервным механизмом для использования при сбое «нормального» разрешения перегрузки, функции, найденные в ADL, столь же жизнеспособны, как и функции, найденные при обычном поиске.

Если ADL был решением для восстановления, тогда вылегко попасть в ловушку, если бы использовалась функция, даже когда была другая функция, которая была лучше подобрана, но видна только через ADL.Это может показаться особенно странным в случае (например) перегрузки операторов.Вы не хотели бы, чтобы два объекта сравнивались через operator== для типов, в которые они могли бы быть неявно преобразованы, когда в соответствующем пространстве имен существует совершенно хороший operator==.

3 голосов
/ 09 сентября 2010

Рассмотрим mystream, который наследуется от std::ostream.Вы хотели бы, чтобы ваш тип поддерживал все операторы <<, которые определены для std::ostream обычно в пространстве имен std.Таким образом, базовые классы являются ассоциированными классами для ADL.

Я думаю, это также следует из принципа подстановки - и функции в пространстве имен класса считаются частью его интерфейса (см. Статью Херба Саттера «Что в классе?»)Поэтому интерфейс, работающий с базовым классом, должен продолжать работать с производным классом.

Вы также можете обойти это, отключив ADL:

(find)( deq, STest2() );
3 голосов
/ 09 сентября 2010

Я добавлю очевидный ответ сам, потому что я только что провел некоторое исследование этой проблемы:

C ++ 03 3.4.2

§2 Для каждого аргумента типа Tв вызове функции есть набор из нуля или более связанных пространств имен [...] Наборы пространств имен и классов определяются следующим образом:

[...]

- Если T является типом класса (включая объединения), его ассоциированными классами являются: сам класс;класс, членом которого он является, если таковой имеется; и его прямые и косвенные базовые классы .Его ассоциированные пространства имен являются пространствами имен, в которых определены связанные с ним классы.

§ 2a Если обычный неквалифицированный поиск имени находит объявление функции-члена класса , ассоциированные пространства имен и классыне рассматриваются.В противном случае набор объявлений, найденных при поиске имени функции, представляет собой объединение набора объявлений, найденных с использованием обычного неквалифицированного поиска, и набора объявлений, найденных в пространствах имен и классах, связанных с типами аргументов.

По крайней мере, это стандартное соответствие, но я до сих пор не понимаю обоснования здесь.

1 голос
/ 09 сентября 2010

Я думаю, вы сами сформулировали проблему:

в глобальном пространстве имен

Функции в глобальном пространстве имен считаются последними. Это самая внешняя сфера по определению . Любая функция с тем же именем (не обязательно применимо), которая находится в более близкой области (с точки зрения вызова), будет выбрана первой.

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);

И, разумеется, разрешение перегрузки не будет установлено, если совпадение не найдено.

Обратите внимание, что поиск по имени чрезвычайно туп: ни арность, ни тип аргумента не учитываются!

Следовательно, в C ++ есть только два вида свободных функций:

  • Те, которые являются частью интерфейса класса, объявленного в том же пространстве имен, подобранного ADL
  • Те, которые не являются, и которые вы должны явно квалифицировать, чтобы избежать проблем этого типа

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

...