Имя шаблона, зависящее от имени

Рассмотрим следующий код:

struct bar
{
  template <typename U>
  void fun0() const {}
};

template <typename T>
struct foo
{
  void
  fun1(const bar& d)
  {
    // (1) KO
    fun2(d).fun0<int>();
    // (2) OK
    fun2(d).template fun0<int>();
    // (3) OK        
    d.fun0<int>();
  }

  bar
  fun2(const bar& d)
  {
    return d;
  }
};

Строки (2) и (3) компилируются, но (1) не выполняется:

error: use 'template' keyword to treat 'fun0' as a dependent template name
    fun2(d).fun0<int>();

            ^
            template 

(Как и ожидалось, если foo больше не является шаблонной структурой, (1) также компилируется)

Почему bar::fun0 имя зависимого шаблона здесь? bar не зависит от параметра шаблона T от foo.

Edit:

Ясно, что bar::fun2 отвечает за двусмысленность, с которым имеет дело .template. Например, добавьте две следующие свободные функции:

bar
fun3(const bar& d)
{
  return d;
}

template <typename T>
T
fun4(const T& d)
{
  return d;
}

fun3(d).fun0<int>() и fun4(d).fun0<int>()) также компилируются в контексте foo::fun1. Таким образом, неоднозначность вызвана параметром шаблона foo.

Почему fun2(d).fun0<int>() не анализируется как вызов шаблона функции-члена?

Ответ 1

Я думаю, что это сводится к правилам в разделе 14.6.2 стандарта. В принципе, я считаю, что правила ошибочны с осторожностью, требуя от вас использовать template и typename - после того, как вы сформируете выражение, которое потенциально может быть зависимым, иногда оно будет объявлено так согласно правилам, хотя человек может ясно видеть, что он на самом деле не зависит. Тогда существует общее правило 14.6.2.2.1, в котором говорится, что

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

Я думаю, что происходит то, что выражение fun2(d) объявляется зависящим от типа правилами, хотя этот тип на самом деле одинаковый во всех экземплярах этого (первичного) шаблона, поскольку вы и я можем см. - поэтому удивительно, что требуется template.

В 14.6.2.1.4 в стандарте С++ 11 (или С++ 14)

Имя является зависимым членом текущего экземпляра, если он является членом текущего экземпляра что при поиске вверх ссылается на хотя бы один член класса, который является текущим экземпляром.

Это означает, что имя fun2 является зависимым именем, хотя fun2 не ссылается на T или что-либо, что явно зависит от T.

Таким образом, когда мы рассмотрим выражение, которое вы указали fun2(d).fun0<int>(), и рассмотрите "подвыражение доступа к члену", то есть fun2(d) . fun0<int> - интуитивно мы хотим сказать что это не зависит, поскольку fun2(d) всегда имеет тип bar, а fun0<int> не зависит от T. Существует правило 14.6.2.2.5, в котором говорится

Выражение доступа к члену класса (5.2.5) зависит от типа, если выражение относится к члену текущего экземпляр и тип ссылочного элемента зависят или выражение доступа к члену класса относится к члену неизвестной специализации.

Ни одно из этих условий не применяется буквально, поскольку bar не совпадает с текущим экземпляром (foo<T>), и ни один из fun0<int> не является членом неизвестной специализации шаблона foo. Вот почему выражение d.fun0<int>() хорошо сформировано.

Однако обратите внимание, что это правило 14.6.2.2.5 происходит после 14.6.2.2.1, которое устанавливает значение всего этого раздела:

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

Таким образом, если любое подвыражение, например fun2, формально "зависит от типа", оно отравляет все выражение и приводит к тому, что подобные правила применяются так, как если бы мы искали членов параметров шаблона или неизвестных специализаций и т.д. и др.

В частности, условие type-dependent означает, что вам нужен префикс template из-за правила 14.2.4:

Когда имя специализации шаблона члена появляется после . или -> в постфиксном выражении или после inested-name-specifier в identified-id, а выражение объекта postfix-expression зависит от типа или спецификатор вложенных имен в идентификаторе с квалификацией ссылается на зависимый тип, но это имя не является членом текущий экземпляр (14.6.2.1), имя шаблона члена должно быть префиксным шаблоном ключевого слова. В противном случае предполагается, что имя называется не-шаблоном.

Пример:

struct X {
  template<std::size_t> X* alloc();
  template<std::size_t> static X* adjust();
};
template<class T> void f(T* p) {
  T* p1 = p->alloc<200>();          // ill-formed: < means less than
  T* p2 = p->template alloc<200>(); // OK: < starts template argument list
  T::adjust<100>();                 // ill-formed: < means less than
  T::template adjust<100>();        // OK: < starts template argument list
}  

- конец примера]

Теперь очень конкретно:

  • В [14.2.4] в постфиксном выражении fun2(d) . fun0<int>, если выражение объекта fun2(d) зависит от типа, тогда вам нужно использовать префикс template для вызова шаблон участника.

  • В разделе [14.6.2.2.1], если fun2 зависит от типа, то это также заставляет fun2(d).

  • И в соответствии с [14.6.2.1.4], поскольку fun2, когда look up ссылается на член шаблона класса foo, этого достаточно, чтобы сделать его зависимым членом текущего экземпляра.

У меня нет абсолютно ясного аргумента из любого из правил, что если имя относится к зависимому члену текущего экземпляра, то это означает, что выражение, соответствующее имени, type-dependent... и я несколько раз искали.

Однако эту точку зрения пропагандируют в @Johannes Schaub-litb высокопрофессиональное (но упрощенное и ненормативное) изложение правил:

Зависимые имена

Стандарт немного неясен о том, что именно является зависимым именем. На простом чтении (вы знаете, принцип наименьшего удивления), все, что он определяет как зависимое имя, является особым случаем для имен функций ниже. Но так как ясно, что T:: x также нуждается в поиске в контексте создания, он также должен быть зависимым именем (к счастью, начиная с середины С++ 14 комитет начал изучать, как исправить это запутанное определение).

Чтобы избежать этой проблемы, я прибегал к простой интерпретации стандартного текста. Из всех конструкций, которые обозначают зависимые типы или выражения, подмножество из них представляет собой имена. Поэтому эти имена являются "зависимыми именами". Название может принимать разные формы - в стандарте говорится:

Имя - это использование идентификатора (2.11), operator-function-id (13.5), идентификатора-функции-id (12.3.2) или идентификатора шаблона (14.2), который обозначает объект или метку (6.6.4, 6.1)

Идентификатор - это просто последовательность символов/цифр, а следующие две являются формой оператора и формы оператора. Последняя форма - имя шаблона. Все это имена, и при обычном использовании в стандарте имя также может включать в себя классификаторы, которые говорят, какое пространство имен или класс следует искать в имени.

Было бы здорово получить более конкретное объяснение этой последней части.