c++ templates c++11 variadic-templates overloading

c++ - clang windows 10



¿Cuál es la función de plantilla más especializada? clang y g++ difieren en eso (2)

Mientras jugaba con plantillas variadas, siguiendo esta pregunta SO (nota: no es obligatorio ir allí para seguir esta pregunta), llegué a un comportamiento diferente de clang (3.8) y g ++ (6.1) para las siguientes funciones de plantilla sobrecargada:

template <class... Ts> struct pack { }; template <class a, class b> constexpr bool starts_with(a, b) { return false; } template <template <typename...> class PACK_A, template <typename...> class PACK_B, typename... Ts1, typename... Ts2> constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) { return true; } int main() { std::cout << std::boolalpha; std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl; }

Código: http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b

Salida

|---|-----------------------------------------------------------------------------| | # |starts_with(a, b) | expected | clang (3.8) | g++ (6.1) | |---|-----------------------------------|-------------|-------------|-------------| | 1 |a: pack<int, float, double>() | false | false | false | | |b: pack<float, int, double>() | | | | |---|-----------------------------------|-------------|-------------|--------- ---| | 2 |a: pack<int, float, double>() | false | false | false | | |b: pack<int, float, double, int>() | | | | |---|-----------------------------------|-------------|-------------|--------- ---| | 3 |a: pack<int, float, double>() | false | false | false | | |b: pack<int, float, int>() | | | | |---|-----------------------------------|-------------|-------------|--------- ---| | 4 |a: pack<int, float, double>() | true | true | false | | |b: pack<int, float, double>() | | | | |---|-----------------------------------|-------------|-------------|--------- ---| | 5 |a: pack<int, float, double>() | true | false | false | | |b: pack<int>() | | | | |---|-----------------------------------------------------------------------------|

Los dos últimos casos (4 y 5) están en cuestión: ¿mi expectativa para la plantilla más especializada es incorrecta? y si es así, ¿quién tiene razón en el caso 4, clang o g ++? (tenga en cuenta que el código se compila sin ningún error o advertencia en ambos, pero con resultados diferentes).

Tratando de responder a eso, revisé varias veces las reglas "más especializadas" en la especificación (14.5.6.2 Ordenamiento parcial de plantillas de funciones) y en cppreference : parece que la regla más especializada dará el resultado que estoy esperando. (Uno puede esperar un error de ambigüedad si no, pero este tampoco es el caso). Entonces, ¿qué me estoy perdiendo aquí?

Espere (1): por favor, no se apure y traiga el " cuestionario de métodos preferidos para no sobrecargar " de Herb Sutter y su plantilla de prueba . ¡Estos son seguramente importantes, pero el lenguaje todavía permite la sobrecarga de plantillas! (De hecho, es un punto de fortalecimiento por el que debería preferir no sobrecargar las plantillas; en algunos casos de borde puede confundir a dos compiladores diferentes o confundir al programador. Pero la pregunta no es si usarlo o no, es: lo que es ¿El comportamiento correcto si lo usas? ).

Espere (2): por favor no se apresure a traer otras soluciones posibles. Hay por seguro. Aquí hay dos: uno con estructura interna y otro con métodos estáticos internos . Ambas son soluciones adecuadas, ambas funcionan como se esperaba, sin embargo, la pregunta sobre el comportamiento de sobrecarga de la plantilla anterior continúa.


Como mencionó Holt, el estándar es muy estricto cuando se trata de la deducción de parámetros de una plantilla variada:

14.8.2.5/9

Si P tiene un formulario que contiene T o i, entonces cada argumento Pi de la lista de argumentos de plantilla respectiva P se compara con el argumento correspondiente Ai de la lista de argumentos de plantilla correspondiente de A. Si la lista de argumentos de plantilla de P contiene una expansión de paquete que no es el último argumento de plantilla, toda la lista de argumentos de plantilla es un contexto no deducido. Si Pi es una expansión de paquete, entonces el patrón de Pi se compara con cada uno de los argumentos restantes en la lista de argumentos de plantilla de A. Cada comparación deduce los argumentos de plantilla para las posiciones subsiguientes en los paquetes de parámetros de plantilla expandidos por Pi.

Esto según lo interpretado por TC significaría que Ts1... puede deducirse del segundo argumento, pero no deja espacio para Ts2... deducción. Como tal, aparentemente, el clang sería lo correcto aquí y gcc sería incorrecto ... La sobrecarga debería elegirse para ello solo si el segundo parámetro contendría exactamente los mismos parámetros de plantilla, por ejemplo:

starts_with(pack<int, float, double>(), pack<int, float, double>())

Aún el ejemplo 5. no cumple con este requisito y no permite que el compilador elija la sobrecarga.


Sólo información: no es una respuesta. Esta es una respuesta a una pregunta en los comentarios:

en gcc5.3, hacer el siguiente pequeño cambio lo induce a producir los resultados esperados, o al menos los mismos resultados que clang.

rhodges@dingbat:~$ cat nod.cpp #include <iostream> using namespace std; template <class... Ts> struct pack { }; template <class a, class b> constexpr bool starts_with(a, b) { return false; } template <typename... Ts1, typename... Ts2 > constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>) { return true; } int main() { std::cout << std::boolalpha; std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl; } rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out false false false true false rhodges@dingbat:~$ g++ --version g++ (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. rhodges@dingbat:~$

y para el registro, modificar el programa para evaluar todos los paquetes en contextos deducidos trae el éxito en ambas plataformas:

rhodges@dingbat:~$ cat nod.cpp #include <iostream> using namespace std; template <class... Ts> struct pack { }; template <class a, class b> constexpr bool starts_with_impl(a, b) { return false; } template<typename...LRest> constexpr bool starts_with_impl(pack<LRest...>, pack<>) { return true; } template<typename First, typename...LRest, typename...RRest> constexpr bool starts_with_impl(pack<First, LRest...>, pack<First, RRest...>) { return starts_with_impl(pack<LRest...>(), pack<RRest...>()); } template <typename... Ts1, typename... Ts2 > constexpr bool starts_with(pack<Ts2...> p1, pack<Ts1...> p2) { return starts_with_impl(p1, p2); } int main() { std::cout << std::boolalpha; std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl; std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl; } rhodges@dingbat:~$ g++ -std=c++14 nod.cpp && ./a.out false false false true true

Gracias a WF por guiarme en esta dirección.