plantillas - plantilla vector c++
¿Por qué se prefiere una elipsis a una plantilla variad cuando se llama sin argumentos? (2)
Cuando T...
está vacío, el compilador realiza una resolución de sobrecarga para determinar cuál de
std::true_type check(); // instantiated from the function template
std::false_type check(...);
es el mejor candidato viable, como se describe en [over.match.best] 13.3.3 / 1 (citando N3936):
Defina ICSi (F) como sigue:
si F es una función miembro estática, ICS1 (F) se define de manera tal que ICS1 (F) no es mejor ni peor que ICS1 (G) para cualquier función G, y, simétricamente, ICS1 (G) no es ni mejor ni peor que ICS1 (F) 132; de otra manera,
deje que ICSi (F) denote la secuencia de conversión implícita que convierte el i-th argumento en la lista al tipo de i-th parámetro de la función viable F. 13.3.3.1 define las secuencias de conversión implícita y 13.3.3.2 define lo que significa para que una secuencia de conversión implícita sea una mejor secuencia de conversión o peor secuencia de conversión que otra.
Dadas estas definiciones, una función viable F1 se define como una función mejor que otra función viable F2 si, para todos los argumentos i, ICSi (F1) no es una secuencia de conversión peor que ICSi (F2), y luego
para algún argumento j, ICSj (F1) es una mejor secuencia de conversión que ICSj (F2), o, si no es así,
el contexto es una inicialización por conversión definida por el usuario (ver 8.5, 13.3.1.5 y 13.3.1.6) y la secuencia de conversión estándar del tipo de retorno de F1 al tipo de destino (es decir, el tipo de la entidad que se está inicializando) es una mejor secuencia de conversión que la secuencia de conversión estándar del tipo de retorno de F2 al tipo de destino. [ Ejemplo:
struct A { A(); operator int(); operator double(); } a; int i = a; // a.operator int() followed by no conversion // is better than a.operator double() followed by // a conversion to int float x = a; // ambiguous: both possibilities require conversions, // and neither is better than the other
- ejemplo final ] o, si no eso,
el contexto es una inicialización por función de conversión para el enlace de referencia directo (13.3.1.6) de una referencia al tipo de función, el tipo de retorno de F1 es el mismo tipo de referencia (es decir, lvalue o rvalue) que la referencia que se está inicializando, y el retorno tipo de F2 no es [ Ejemplo:
template <class T> struct A { operator T&(); // #1 operator T&&(); // #2 }; typedef int Fn(); A<Fn> a; Fn& lf = a; // calls #1 Fn&& rf = a; // calls #2
- ejemplo final ] o, si no eso,
F1 no es una especialización de plantilla de función y F2 es una especialización de plantilla de función o, si no es eso,
- F1 y F2 son especializaciones de plantilla de función, y la plantilla de función para F1 es más especializada que la plantilla para F2 de acuerdo con las reglas de ordenamiento parcial descritas en 14.5.6.2.
En este caso, las secuencias de conversión para ambos candidatos están vacías ya que no hay argumentos. La segunda bala es el factor decisivo:
- F1 no es una especialización de plantilla de función y F2 es una especialización de plantilla de función o, si no es eso,
por lo tanto, la plantilla std::false_type check(...);
se prefiere.
Mi solución preferida, obviamente hay muchas, sería hacer ambas plantillas de candidatos y discriminar a través de la conversión de puntos suspensivos [over.ics.ellipsis] 13.3.3.1.3 / 1:
Una secuencia de conversión de puntos suspensivos se produce cuando un argumento en una llamada de función coincide con la especificación del parámetro de puntos suspensivos de la función llamada (ver 5.2.2).
al dar a la declaración de plantilla "preferida" un parámetro extraño que es una mejor coincidencia, ya que se preferirá cualquier otra secuencia de conversión a una conversión de puntos suspensivos por [over.ics.rank] 13.3.3.2/2:
Al comparar las formas básicas de secuencias de conversión implícitas (como se define en 13.3.3.1)
- una secuencia de conversión estándar (13.3.3.1.1) es una mejor secuencia de conversión que una secuencia de conversión definida por el usuario o una secuencia de conversión de puntos suspensivos, y
- una secuencia de conversión definida por el usuario (13.3.3.1.2) es una mejor secuencia de conversión que una secuencia de conversión de puntos suspensivos (13.3.3.1.3).
Example :
template<typename... T,
typename = decltype(f(std::declval<T>()...))>
std::true_type check(int);
template<typename...>
std::false_type check(...);
template<typename... T> using Predicate = decltype(check<T...>(0));
Estoy usando el siguiente patrón SFINAE para evaluar un predicado en una lista de tipos variadic:
#include <type_traits>
void f(int = 0); // for example
template<typename... T,
typename = decltype(f(std::declval<T>()...))>
std::true_type check(T &&...);
std::false_type check(...);
template<typename... T> using Predicate = decltype(check(std::declval<T>()...));
static_assert(!Predicate<int, int>::value, "!!");
static_assert( Predicate<int>::value, "!!");
static_assert( Predicate<>::value, "!!"); // fails
int main() {
}
Para mi sorpresa, la sobrecarga de puntos suspensivos se selecciona cuando se llama a check
con una lista de argumentos vacía, por lo que Predicate<>
is std::false_type
incluso cuando la expresión SFINAE es válida.
¿No deberían las plantillas de función variable siempre ser preferibles a las funciones de puntos suspensivos?
¿Hay alguna solución?
Eso también me sorprende.
Una solución alternativa podría ser pasar un int
(por ejemplo, 0
) como primer argumento para check()
y forzar al compilador a probar primero la versión de la plantilla:
template<typename... T, typename = decltype(f(std::declval<T>()...))>
std::true_type check(int &&, T &&...); //ADDED `int &&` as the first parameter type
std::false_type check(...);
template<typename... T> using Predicate = decltype(check(0, std::declval<T>()...));
Tenga en cuenta que el temporal creado a partir de 0
intentará vincularse a int&&
primero (y eso es muy importante aquí), si el valor-sfinae falla, entonces intentará la segunda sobrecarga.
Espero que ayude.