Подтвердить что ты не робот

Что такое "непосредственный контекст", упомянутый в стандарте С++ 11, для которого применяется SFINAE?

В параграфе 14.8.2/8 Стандарта С++ 11 указаны условия, при которых отказ замены должен или не должен приводить к "жесткой" ошибке компиляции (что приводит к сбою компиляции) или "мягкой" ошибке что просто заставит компилятор отбросить шаблон из набора кандидатов для разрешения перегрузки (без сбоя компиляции и включения известной идиомы SFINAE):

Если подстановка приводит к недопустимому типу или выражению, тип дедукции не выполняется. Недопустимый тип или выражение это тот, который был бы плохо сформирован, если он был написан с использованием замещенных аргументов. [Примечание: проверка доступа выполняется как часть процесса замещения. -end note] Только недопустимые типы и выражения в непосредственном контексте тип функции и ее типы параметров шаблона могут привести к отказу от вычета. [...]

Слова "непосредственный контекст" появляются только 8 раз во всем стандарте С++ 11, и каждый раз, когда за ними следует (или происходит как часть) экземпляр следующего (ненормативного) текста:

[Примечание: оценка замещенных типов и выражений может привести к побочным эффектам, таким как создание шаблона класса специализации и/или функции шаблонов, генерация неявно определенных функций и т.д. Такие побочные эффекты не находятся в "непосредственном контексте" и могут привести к плохому формированию программы. примечание]

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

Вопрос:

Не могли бы вы предоставить объяснение, процедуру принятия решений и/или некоторые конкретные примеры, чтобы помочь выяснить, в каких случаях ошибка замещения выполняется и не встречается в "непосредственном контексте" типа функции и ее типов параметров шаблона?

4b9b3361

Ответ 1

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

Если все эти экземпляры и неявно-определения (которые могут включать определение функций как удаленных) могут быть выполнены без ошибок, то любые дальнейшие "ошибки", возникающие во время подстановки (т.е. при обращении к экземпляру шаблонов и неявно определенным функциям в подпись шаблона функции) не являются ошибками, но приводят к ошибкам вычета.

Итак, задайте шаблон функции следующим образом:

template<typename T>
void
func(typename T::type* arg);

и "fall-back", которые будут использоваться, если дедукция не будет выполнена для другой функции:

template<typename>
void
func(...);

и шаблон класса следующим образом:

template<typename T>
  struct A
  {
    typedef T* type;
  };

Вызов func<A<int&>>(nullptr) заменит A<int&> на T, и чтобы проверить, существует ли T::type, он должен создать экземпляр A<int&>. Если мы предположим, что перед вызовом func<A<int&>(nullptr) было создано явное инстанцирование:

template class A<int&>;

то это не сработает, потому что он пытается создать тип int&*, а указатели на ссылки не разрешены. Мы не добираемся до проверки того, что замена выполняется успешно, потому что существует сложная ошибка при создании экземпляра A<int&>.

Теперь скажем там явную специализацию A:

template<>
  struct A<char>
  {
  };

Вызов func<A<char>>(nullptr) требует создания экземпляра A<char>, поэтому представьте себе явное инстанцирование где-то в программе перед вызовом:

template class A<char>;

Этот экземпляр в порядке, нет ошибки, поэтому переходим к замене аргументов. Создание экземпляра A<char> сработало, но A<char>::type не существует, но это нормально, потому что он ссылается только на объявление func, поэтому только вывод аргумента не выполняется, а функция fall-back ... получает вместо этого.

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

Таким образом, ментальная модель, которую я использую, заключается в том, что подстановка должна сначала выполнить "подготовительный" шаг для генерации типов и членов, что может привести к жестким ошибкам, но как только у нас будет все необходимое поколение, любые дополнительные недопустимые применения не являются ошибками, Конечно, все это значит переместить проблему из "что означает немедленный контекст"? к "Какие типы и члены должны быть созданы до того, как эту замену можно проверить?" так что это может или не поможет вам!

Ответ 2

Непосредственный контекст - это в основном то, что вы видите в самом объявлении шаблона. Все, что находится вне этого, является жесткой ошибкой. Примеры жестких ошибок:

#include <type_traits>

template<class T>
struct trait{ using type = typename T::type; };

template<class T, class U = typename trait<T>::type>
void f(int);
void f(...);

template<class T, class U = typename T::type>
void g(int);
void g(...);

template<class>
struct dependent_false : std::false_type{};

template<class T>
struct X{
    static_assert(dependent_false<T>(), "...");
    using type = void;
};

int main(){
    f<int>(0);
    g<X<int>>(0);
}

Текущая версия.