Por que o decaimento do ponteiro tem prioridade sobre um modelo deduzido?

Digamos que eu esteja escrevendo uma function para imprimir o tamanho de uma string:

template  void foo(const char (&s)[N]) { std::cout << "array, size=" << N-1 << std::endl; } foo("hello") // prints array, size=5 

Agora eu quero estender o foo para suportar non -arrays:

 void foo(const char* s) { std::cout << "raw, size=" << strlen(s) << std::endl; } 

Mas acontece que isso quebra meu uso pretendido original:

 foo("hello") // now prints raw, size=5 

Por quê? Isso não exigiria uma conversão de matriz para ponteiro, enquanto o modelo seria uma correspondência exata? Existe uma maneira de garantir que minha function de matriz seja chamada?

A razão fundamental para essa ambigüidade (em conformidade com o padrão) parece estar dentro do custo de conversão: A resolução de sobrecarga tenta minimizar as operações realizadas para converter um argumento no parâmetro correspondente. Uma matriz é efetivamente o ponteiro para seu primeiro elemento, decorado com algumas informações do tipo em tempo de compilation. Uma conversão de matriz para ponteiro não custa mais do que, por exemplo, salvar o endereço do próprio array ou inicializar uma referência a ele. Dessa perspectiva, a ambigüidade parece justificada, embora conceitualmente não seja intuitiva (e possa ser inferior). De fato, esta argumentação se aplica a todas as Transformações de Lvalue, como sugerido pela citação abaixo. Outro exemplo:

 void g() {} void f(void(*)()) {} void f(void(&)()) {} int main() { f(g); // Ambiguous } 

O seguinte é obrigatório standardese. Funções que não são especializações de alguns gabaritos de funções são preferíveis àquelas que são se ambas forem igualmente boas correspondências (veja [over.match.best] / (1.3), (1.6)). Em nosso caso, a conversão realizada é uma conversão de matriz para ponteiro, que é uma sorting de Transformação de Lvalue com Correspondência Exata (de acordo com a tabela 12 em [over.ics.user]). [over.ics.rank] / 3:

  • A sequência de conversão padrão S1 é uma sequência de conversão melhor que a sequência de conversão padrão S2 se

    • S1 é uma subsequência apropriada de S2 (comparando as seqüências de conversão na forma canônica definida por 13.3.3.1.1, excluindo qualquer transformação de Lvalue ; a seqüência de conversão de identidade é considerada como uma subsequência de qualquer sequência de conversão não identitária) ou, se isso não,

    • a sorting de S1 é melhor que a sorting de S2 , ou S1 e S2 têm a mesma sorting e são distinguíveis pelas regras no parágrafo abaixo, ou, se não isso,

    • [..]

O primeiro ponto de bala exclui nossa conversão (como é uma Transformação Lvalue). O segundo exige uma diferença nas classificações, o que não está presente, já que ambas as conversões possuem sorting de correspondência Exata; As “regras no parágrafo abaixo”, ou seja, em [over.ics.rank] / 4, também não abrangem as conversões de array para ponteiro.
Então acredite ou não, nenhuma das duas seqüências de conversão é melhor que a outra, e assim o char const* -overload é escolhido.


Solução alternativa possível: Defina a segunda sobrecarga como um modelo de function, então a ordenação parcial entra em ação e seleciona a primeira.

 template  auto foo(T s) -> std::enable_if_t{}> { std::cout < < "raw, size=" << std::strlen(s) << std::endl; } 

Demo