Принудительный поиск имени с учетом области пространства имен

Этот вопрос несколько связан с точкой создания экземпляра и привязкой имени, но не совсем. Вопрос касается стандарта и того, как он разрешает поиск символов внутри определений шаблонов.

Рассмотрим этот пример, основанный на библиотеке ostream:

// Output module
class Output {
 public:
  void operator<<(int);
  void operator<<(double);
  ...
};

// Item module
class Item {
  friend void operator<<(Output& obj, const Item& x) {
     ...
  }
};

// Main program
int main() {
  Output out;
  Item item;
  out << 3;
  out << 2.0;
  out << item;
}

В этом примере ключевым моментом является то, что модуль вывода определяется перед любыми модулями, которые его используют, и есть один модуль (Item модуль), который использует модуль вывода для создания элементов.

Это позволяет определять базовые операторы emit внутри класса Output, но любой модуль, который определяет новые классы и хочет предоставить метод emit, может сделать это, предоставив дружественную функцию с двумя аргументами. Пока все в порядке.

Теперь давайте попробуем использовать ту же идею без перегрузки операторов и вместо этого использовать функции-члены плана для предопределенных функций-эмитентов для базового типа и по-прежнему позволять определять функции-эмитенты для конкретного класса как дружественные функции для класса:

class Output {
 public:
  template <class Type>
  void emit(Type x) {
    emit(*this, x);
  }

  void emit(int);
  void emit(double);
};

class Item {
  friend void emit(Output& obj, const Item& x) {
    ...
  }
  ...
};

int main() {
  Output out;
  Item item;
  out.emit(3);
  out.emit(2.0);
  out.emit(item);
}

По сравнению с предыдущим кодом добавлена ​​функция шаблона, потому что нет необходимости иметь разные соглашения о вызовах в зависимости от типа. Другими словами, должна быть возможность использовать соглашение out.emit(...) независимо от того, какой элемент генерируется.

Однако при компиляции этого (используя GCC 4.8.4) мы получаем следующую ошибку:

example.cc: In instantiation of ‘void Output::emit(Type) [with Type = Item]’:
example.cc:49:20:   required from here
example.cc:33:9: error: no matching function for call to ‘Output::emit(Output&, Item&)’
         emit(*this, x);
         ^
example.cc:33:9: note: candidates are:
example.cc:32:12: note: template<class Type> void Output::emit(Type)
       void emit(Type x) {
            ^
example.cc:32:12: note:   template argument deduction/substitution failed:
example.cc:33:9: note:   candidate expects 1 argument, 2 provided
         emit(*this, x);
         ^
example.cc:36:12: note: void Output::emit(int)
       void emit(int) {
            ^
example.cc:36:12: note:   candidate expects 1 argument, 2 provided
example.cc:37:12: note: void Output::emit(double)
       void emit(double) {
            ^
example.cc:37:12: note:   candidate expects 1 argument, 2 provided

Другими словами, функция emit верхнего уровня никогда не рассматривается, а вместо этого при разрешении имени учитываются только функции-члены внутри класса Output.

Я предположил, что это произошло потому, что символ emit не был зависимым именем и, следовательно, просматривался в точке определения (шаблона), а не в точке создания, но в разделе 14.6.2 §1 стандарта С++ говорится (слегка отредактировано) :

[...] В выражении вида:

постфиксное-выражение ( список-выражений opt )

где постфиксное-выражение является идентификатором, идентификатор обозначает зависимое имя тогда и только тогда, когда любое из выражений в списке-выражений — выражение, зависящее от типа (14.6.2.2).

И далее, в 14.6.2.2 («Выражения, зависящие от типа») §2 сказано:

this зависит от типа, если тип класса включающей функции-члена зависит

Что, AIUI, имеет место здесь.

Итак, вопросы:

  1. Почему поиск не учитывает версию верхнего уровня emit в разрешении имени здесь?
  2. Можно ли заставить второй пример работать так же, как и первый, чтобы определение функции-члена шаблона учитывало как функции-члены, так и функции области видимости пространства имен в момент создания экземпляра?

Update: Изменил название поста на более точное и сделал небольшую правку цитат из стандарта.


person Mats Kindahl    schedule 15.11.2015    source источник
comment
Потому что, если функция-член найдена (emit является членом), ADL не происходит. И единственное, что происходит в момент инстанцирования — это ADL-lookup. Заставьте его найти функцию, не являющуюся членом (найдите использование std::swap; идиома). namespace adlenabler { void emit(); }; (делегируйте это в свой класс.. мне лень это показывать :)) и вместо этого напишите using adlenabler::emit; emit(*this, x);   -  person Johannes Schaub - litb    schedule 15.11.2015
comment
Хм? У вас есть идея, где в стандарте это описано?   -  person Mats Kindahl    schedule 15.11.2015
comment
Ах забыл, что я сказал о делегировании членам. В любом случае это не было задумано, и у них недостаточно параметров. Ну, остальные вещи, которые я сказал, все еще применимы :)   -  person Johannes Schaub - litb    schedule 15.11.2015
comment
Мне удалось заставить его работать, используя ваше предложение. Я поместил класс Output и функцию emit в отдельное пространство имен и добавил using в функцию-член шаблона. Если вы добавите ответ здесь, я отмечу его для вас.   -  person Mats Kindahl    schedule 15.11.2015
comment
Я отправлю ответ, чтобы прояснить ситуацию. По-прежнему отсутствует абзац для объявления использования, чтобы объяснить, почему это работает.   -  person Mats Kindahl    schedule 16.11.2015


Ответы (1)


Как указывает Johannes, если член функция найдена ADL не вызывается, и ADL необходим, чтобы найти объявление друга Item.

Итак, чтобы ответить на первый вопрос, в разделе 3.4.2 §2 стандарта С++ (версия С++ 03) говорится:

Если обычный неквалифицированный поиск имени находит объявление функции-члена класса, связанные пространства имен и классы не учитываются. [...]

Чтобы ответить на второй вопрос, вот фиксированный пример кода:

namespace emitter {

class Output;

template <class Type>
void emit(Output& out, const Type& t) {
  emit(out, t);
}

class Output {
 public:
  template <class Type>
  void emit(Type x) {
    using emitter::emit;
    emit(*this, x);
  }

  void emit(int);
  void emit(double);
};

}

class Item {
  friend void emit(emitter::Output& obj, const Item& x) {
    ...
  }
};

Однако я все еще пытаюсь найти абзац, разъясняющий, почему объявление using решает проблему. Самое близкое, что я могу найти сейчас, это 7.3.3 §13:

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

Но это относится к using B::f в классе D, производном от B, поэтому не идеальное совпадение.

person Mats Kindahl    schedule 16.11.2015