Принудително търсене на име, за да вземе предвид обхвата на пространството от имена

Този въпрос е донякъде свързан с точката на инстанциране и обвързването на име, но не точно. Въпросът е за стандарта и как разрешава търсенето на символи в дефинициите на шаблона.

Помислете за този пример, базиран на библиотеката 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 модул), който използва изходния модул за излъчване на елементи.

Това позволява базовите оператори за излъчване да бъдат дефинирани вътре в класа Output, но всеки модул, който дефинира нови класове и иска да предостави метод за излъчване, може да го направи, като предостави функция приятел с два аргумента. Всичко наред досега.

Сега нека се опитаме да използваме същата идея без претоварване на оператора и вместо това да използваме функции на член на плана за предварително дефинираните излъчващи функции за базовия тип и все пак да позволим на специфичните за класа излъчващи функции да бъдат дефинирани като приятелски функции за класа:

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 в стандарта C++ казва (леко редактиран) :

[...] В израз на формата:

постфикс-израз ( списък с изрази opt )

където постфиксният-израз е идентификатор, идентификаторът обозначава зависимо име ако и само ако някой от изразите в списък-изрази е зависим от типа израз (14.6.2.2).

И по-нататък, в 14.6.2.2 ("Изрази, зависими от типа") §2 се казва:

this е зависим от типа, ако типът на класа на обхващащата функция член е зависим

Което, AIUI, е случаят тук.

И така, въпросите са:

  1. Защо търсенето не взема предвид версията от най-високо ниво на emit в резолюцията на име тук?
  2. Възможно ли е вторият пример да работи по същия начин като първия, така че дефиницията на функция член на шаблона да вземе предвид както функциите член, така и функциите на обхвата на пространството от имена в точката на инстанциране?

Актуализация: Променено е заглавието на публикацията, за да бъде по-точно и е направена лека редакция на цитатите от стандарта.


person Mats Kindahl    schedule 15.11.2015    source източник
comment
Тъй като ако бъде намерена функция член (emit е член), ADL не се случва. И единственото нещо, което се случва в точката на създаване е ADL-търсене. Накарайте го да намери функция, която не е член (потърсете използвайки 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 в стандарта C++ (C++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-декларация в производен клас, ще бъдат третирани като членове на производния клас.

Но това се отнася до using B::f в клас D, получен от B, така че не е перфектно съвпадение.

person Mats Kindahl    schedule 16.11.2015