¿Por qué el decaimiento del puntero tiene prioridad sobre una plantilla deducida?


Digamos que estoy escribiendo una función para imprimir la longitud de una cadena:

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

foo("hello") // prints array, size=5

Ahora quiero extender foo para soportar no -arrays:

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

Pero resulta que esto rompe mi uso original previsto:

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

¿Por qué? ¿No requeriría eso una conversión de matriz a puntero, mientras que la plantilla sería una coincidencia exacta? ¿Hay alguna manera de asegurar que se llame a mi función array?

Author: Barry, 2015-01-30

1 answers

La razón fundamental de esta ambigüedad (conforme al estándar) parece estar dentro del costo de conversión: La resolución de sobrecarga intenta minimizar las operaciones realizadas para convertir un argumento al parámetro correspondiente. Sin embargo, un array es efectivamente el puntero a su primer elemento, decorado con alguna información de tipo en tiempo de compilación. Una conversión de matriz a puntero no cuesta más que, por ejemplo, guardar la dirección de la matriz en sí, o inicializar una referencia a ella. Desde esa perspectiva, la ambigüedad parece justificada, aunque conceptualmente es poco intuitiva (y puede ser subpar). De hecho, esta argumentación se aplica a todas las transformaciones Lvalue, como sugiere la cita a continuación. Otro ejemplo:

void g() {}

void f(void(*)()) {}
void f(void(&)()) {}

int main() {
    f(g); // Ambiguous
}

Lo siguiente es obligatorio. Las funciones que no son especializaciones de alguna plantilla de función son preferidas a las que lo son si ambas coinciden igualmente (ver [over.coincidir.mejor]/(1.3), (1.6)). En nuestro caso, el la conversión realizada es una conversión de matriz a puntero, que es una Transformación Lvalue con un rango de Coincidencia Exacto (de acuerdo con la tabla 12 en [over.ics.usuario]). [sobre.ics.rango] / 3:

  • La secuencia de conversión estándar S1 es una mejor secuencia de conversión que la secuencia de conversión estándar S2 si

    • S1 es una subsecuencia propia de S2 (comparando las secuencias de conversión en la forma canónica definida por 13.3.3.1.1, excluyendo cualquier Transformación Lvalue ; la secuencia de conversión de identidad se considera una subsecuencia de cualquier conversión sin identidad secuencia) o, si no es así,

    • El rango de S1 es mejor que el rango de S2, o S1 y S2 tienen el mismo rango y se distinguen por las reglas en el párrafo siguiente, o, si no es eso,

    • [..]

El primer punto excluye nuestra conversión (ya que es un Lvalue Transformación). El segundo requiere una diferencia en los rangos, que no está presente, ya que ambas conversiones tienen un rango de coincidencia exacto; Las "reglas del párrafo siguiente", es decir, en [over.ics.rank] / 4, tampoco cubre las conversiones de matriz a puntero.
Así que lo creas o no, ninguna de las dos secuencias de conversión es mejor que la otra, y por lo tanto se elige la char const*-sobrecarga.


Posible solución: Defina la segunda sobrecarga como una plantilla de función también, luego se inicia el ordenamiento parcial y selecciona la primera.

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

Demo.

 43
Author: Columbo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-26 17:46:01