O que exatamente é o “contexto imediato” mencionado no Padrão C ++ 11 para o qual a SFINAE se aplica?

O parágrafo 14.8.2 / 8 da norma C ++ 11 especifica as condições sob as quais uma falha de substituição deve ou não resultar em um erro de compilation “difícil” (fazendo com que a compilation falhe) ou em um erro “soft” que apenas faça com que o compilador descarte um modelo de um conjunto de candidatos para resolução de sobrecarga (sem fazer a compilation falhar e ativar o conhecido idioma SFINAE):

Se uma substituição resultar em um tipo ou expressão inválida, digite dedução. Um tipo ou expressão inválida é aquela que seria mal formada se escrita usando os argumentos substituídos. [Nota: A verificação de access é feita como parte do processo de substituição. —End note] Apenas tipos e expressões inválidos no contexto imediato do tipo de function e seus tipos de parâmetro de modelo podem resultar em uma falha de dedução . […]

As palavras ” contexto imediato ” aparecem apenas 8 vezes em todo o C ++ 11 Standard, e cada vez que elas são seguidas por (ou ocorrem como parte de) uma instância do seguinte texto (não normativo):

[Nota: A avaliação dos tipos e expressões substituídos pode resultar em efeitos colaterais como a instanciação de especializações de modelos de classs e / ou especializações de modelos de funções, a geração de funções definidas implicitamente, etc. Tais efeitos colaterais não estão no “imediato”. contexto ”e pode resultar no mal-estar do programa. – end note]

A nota dá uma dica (não muito generosa) sobre o que se entende por contexto imediato , mas pelo menos para mim isso muitas vezes não é suficiente para decidir se uma substituição é ou não deve causar um erro de compilation “difícil”.

QUESTÃO:

Você poderia fornecer uma explicação, um procedimento de decisão e / ou alguns exemplos concretos para ajudar a descobrir em quais casos um erro de substituição ocorre e não ocorre no ” contexto imediato ” do tipo de function e seus tipos de parâmetro de modelo?

Se você considerar todos os modelos e funções definidas implicitamente necessárias para determinar o resultado da substituição do argumento de modelo, e imagine que eles sejam gerados primeiro, antes que a substituição comece, então quaisquer erros ocorridos na primeira etapa não estão no contexto imediato, e resultar em erros difíceis.

Se todas essas instanciações e definições implícitas (que podem include a definição de funções como excluídas) podem ser feitas sem erros, então quaisquer “erros” adicionais que ocorram durante a substituição (ie, referindo-se aos modelos instanciados e funções definidas implicitamente nos modelos da function) assinatura) não são erros, mas resultam em falhas de dedução.

Então, dado um modelo de function como este:

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

e um “fall-back” que será usado se a dedução falhar para a outra function:

 template void func(...); 

e um modelo de class como este:

 template struct A { typedef T* type; }; 

Uma chamada para func>(nullptr) replaceá A por T e para verificar se T::type existe deve instanciar A . Se imaginarmos colocar uma instanciação explícita antes da chamada para func(nullptr) :

 template class A; 

então isso falharia, porque ele tenta criar o tipo int&* e os pointers para referências não são permitidos. Não chegamos ao ponto de verificar se a substituição é bem-sucedida, porque há um erro grave ao instanciar A .

Agora vamos dizer que há uma especialização explícita de A :

 template<> struct A { }; 

Uma chamada para func>(nullptr) requer a instanciação de A , então imagine uma instanciação explícita em algum lugar do programa antes da chamada:

 template class A; 

Esta instanciação está OK, não há erro disso, então continuamos a substituição de argumentos. A instanciação de A funcionou, mas A::type não existe, mas tudo bem, porque é apenas referenciado na declaração de func , então apenas faz com que a dedução de argumentos falhe, e o fall-back ... function é chamada em seu lugar.

Em outras situações, a substituição pode fazer com que funções-membro especiais sejam definidas implicitamente, possivelmente como excluídas, o que pode acionar outras instanciações ou definições implícitas. Se ocorrerem erros durante o estágio “gerando instanciações e definições implícitas”, eles serão erros, mas se isso for bem-sucedido, mas durante a substituição, uma expressão na assinatura do modelo de function será inválida, por exemplo, porque usa um membro que não existe ou algo que foi implicitamente definido como excluído, isso não é um erro, apenas uma falha de dedução.

Assim, o modelo mental que uso é que a substituição precisa primeiro de uma etapa de “preparação” para gerar tipos e membros, o que pode causar erros graves, mas, depois de fazer toda a geração necessária, quaisquer outros usos inválidos não são erros. Claro que tudo isso faz mover o problema de “o que significa contexto imediato ?” para “Quais tipos e membros precisam ser gerados antes que essa substituição possa ser verificada?” então pode ou não ajudá-lo!

O contexto imediato é basicamente o que você vê na própria declaração de modelo. Tudo fora disso é um erro difícil. Exemplos de erros graves:

 #include  template struct trait{ using type = typename T::type; }; template::type> void f(int); void f(...); template void g(int); void g(...); template struct dependent_false : std::false_type{}; template struct X{ static_assert(dependent_false(), "..."); using type = void; }; int main(){ f(0); g>(0); } 

Versão ao vivo.