c++ - guia - ¿Cuál es la manera correcta de arreglar la ambigüedad de la resolución de esta plantilla?
qgis manual (5)
AFAIK, sfinae es aplicable a los parámetros de función, así que intente agregar un parámetro de tipo dependiente con el valor predeterminado
template <typename T>
void foo(typename std::enable_if_t<std::is_integral<T>::value>* = 0)
{ std::cout << "T is integral." << std::endl; }
template <typename T>
void foo(typename std::enable_if_t<!std::is_integral<T>::value>* = 0)
{ std::cout << "Any T." << std::endl; }
Supongamos que he escrito:
template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
void foo() { std::cout << "T is integral." << std::endl; }
template <typename T>
void foo() { std::cout << "Any T." << std::endl; }
int main() { foo<short>(); }
Cuando compilo esto, recibo un error sobre la ambigüedad de la llamada (y ningún error si, por ejemplo, sustituyo el short
por float
). ¿Cómo debo arreglar este código para obtener la versión superior para los tipos integrales y la versión inferior de lo contrario?
Los puntos de bonificación si su sugerencia se ajusta al caso de varias versiones especializadas de foo()
además de la general.
De una sola mano:
template <typename T, typename std::enable_if_t<std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "T is integral." << std::endl; }
template <typename T, typename std::enable_if_t<not std::is_integral<T>::value>* = nullptr>
void foo() { std::cout << "Any T." << std::endl; }
Otra forma es diferir a un objeto de función de plantilla:
template<class T, typename = void>
struct foo_impl
{
void operator()() const {
std::cout << "Any T." << std::endl;
}
};
template<class T>
struct foo_impl<T, std::enable_if_t<std::is_integral<T>::value>>
{
void operator()() const {
std::cout << "T is integral." << std::endl;
}
};
template<class T>
void foo() {
return foo_impl<T>()();
}
Me gusta el enfoque de Xeo para este problema. Vamos a hacer un despacho de etiquetas con una alternativa. Cree una estructura de elección que se herede de sí misma hasta el final:
template <int I>
struct choice : choice<I + 1> { };
template <> struct choice<10> { }; // just stop somewhere
Por lo tanto, la choice<x>
se puede convertir a la choice<y>
para x < y
, lo que significa que la choice<0>
es la mejor opción. Ahora, necesitas un último caso:
struct otherwise{ otherwise(...) { } };
Con esa maquinaria, podemos reenviar nuestra plantilla de función principal con un argumento adicional:
template <class T> void foo() { foo_impl<T>(choice<0>{}); }
Y luego haga su mejor elección integral y su peor opción ... cualquier cosa:
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(choice<0> ) {
std::cout << "T is integral." << std::endl;
}
template <typename T>
void foo_impl(otherwise ) {
std::cout << "Any T." << std::endl;
}
Esto hace que sea muy fácil agregar más opciones en el medio. Solo agregue una sobrecarga para la choice<1>
o la choice<2>
o lo que sea. Tampoco hay necesidad de condiciones de separación. La resolución de sobrecarga preferencial para la choice<x>
se encarga de eso.
Aún mejor si además pasas la T
como argumento, porque la sobrecarga es mucho mejor que la especialización:
template <class T> struct tag {};
template <class T> void foo() { foo_impl(tag<T>{}, choice<0>{}); }
Y luego puedes ir salvaje
// special 1st choice for just int
void foo_impl(tag<int>, choice<0> );
// backup 1st choice for any integral
template <class T, class = std::enable_if_t<std::is_integral<T>::value>>
void foo_impl(tag<T>, choice<0> );
// 2nd option for floats
template <class T, class = std::enable_if_t<std::is_floating_point<T>::value>>
void foo_impl(tag<T>, choice<1> );
// 3rd option for some other type trait
template <class T, class = std::enable_if_t<whatever<T>::value>>
void foo_impl(tag<T>, choice<2> );
// fallback
template <class T>
void foo_impl(tag<T>, otherwise );
Una forma de hacer esto es:
template <typename T>
std::enable_if_t<std::is_integral<T>::value, void> foo () {
std::cout << "integral version" << std::endl;
}
template <typename T>
std::enable_if_t<!std::is_integral<T>::value, void> foo () {
std::cout << "general version" << std::endl;
}
con uso:
foo<int> ();
foo<double> ();
struct X {};
foo<X> ();
la salida es:
integral version
general version
general version
Una opción más usando el envío de etiquetas (C ++ 11):
#include <iostream>
void foo_impl(std::false_type) {
std::cout << "Any T." << std::endl;
}
void foo_impl(std::true_type) {
std::cout << "T is integral." << std::endl;
}
template <typename T>
void foo() {
foo_impl(std::is_integral<typename std::remove_reference<T>::type>());
//foo_impl(std::is_integral<typename std::remove_reference_t<T>>()); // C++14
}
int main() {
foo<short>(); // --> T is integral.
foo<short&>(); // --> T is integral.
foo<float>(); // --> Any T.
}
Tomado de Scott Meyers Effective Modern C ++ item 27.