programacion minimos minimo maximos maximo lenguaje funcion calcular c++ templates c++11 variadic-templates

c++ - funcion - maximos y minimos en lenguaje c



Implementando variad min/max funciones (4)

4) Esta es una forma posible de implementar una versión constexpr de esta función:

#include <iostream> #include <type_traits> template <typename Arg1, typename Arg2> constexpr typename std::common_type<Arg1, Arg2>::type vmin(Arg1&& arg1, Arg2&& arg2) { return arg1 < arg2 ? std::forward<Arg1>(arg1) : std::forward<Arg2>(arg2); } template <typename Arg, typename... Args> constexpr typename std::common_type<Arg, Args...>::type vmin(Arg&& arg, Args&&... args) { return vmin(std::forward<Arg>(arg), vmin(std::forward<Args>(args)...)); } int main() { std::cout << vmin(3, 2, 1, 2, 5) << std::endl; std::cout << vmin(3., 1.2, 1.3, 2., 5.2) << std::endl; }

Ver ejemplo en vivo .

Edición: Como @Yakk señaló en los comentarios, el código std::forward<Arg1>(arg1) < std::forward<Arg2>(arg2) ? std::forward<Arg1>(arg1) : std::forward<Arg2>(arg2) std::forward<Arg1>(arg1) < std::forward<Arg2>(arg2) ? std::forward<Arg1>(arg1) : std::forward<Arg2>(arg2) puede causar problemas en algunas situaciones. arg1 < arg2 ? std::forward<Arg1>(arg1) : std::forward<Arg2>(arg2) arg1 < arg2 ? std::forward<Arg1>(arg1) : std::forward<Arg2>(arg2) es la variante más apropiada en este caso.

Estoy implementando variad min / max funciones. Un objetivo es aprovechar el número de argumentos conocidos en tiempo de compilación y realizar una evaluación desenrollada (evitar bucles de tiempo de ejecución). El estado actual del código es el siguiente (presentar min - max es similar)

#include <iostream> using namespace std; template<typename T> T vmin(T val1, T val2) { return val1 < val2 ? val1 : val2; } template<typename T, typename... Ts> T vmin(T val1, T val2, Ts&&... vs) { return val1 < val2 ? vmin(val1, std::forward<Ts>(vs)...) : vmin(val2, std::forward<Ts>(vs)...); } int main() { cout << vmin(3, 2, 1, 2, 5) << endl; cout << vmin(3., 1.2, 1.3, 2., 5.2) << endl; return 0; }

Ahora esto funciona , pero tengo algunas preguntas / problemas :

  1. La sobrecarga no variada tiene que aceptar sus argumentos por valor. Si intento pasar otros tipos de ref tengo los siguientes resultados

    • Referencias universales && -> error de compilación
    • const referencias const& -> OK
    • Referencias planas & -> error de compilación

    Ahora sé que las plantillas de funciones se mezclan de forma extraña con las plantillas, pero ¿hay algún conocimiento específico para la mezcla a mano? ¿Para qué tipo de argumentos debo optar?

  2. ¿No sería suficiente la expansión del paquete de parámetros? ¿Realmente necesito enviar mis argumentos a la llamada recursiva?

  3. ¿Esta funcionalidad se implementa mejor cuando se envuelve dentro de una estructura y se expone como una función miembro estática? ¿La capacidad de especialización parcial me compraría algo?

  4. ¿Existe una implementación / diseño más robusto / eficiente para la versión de la función? (En particular, me pregunto si una versión de constexpr sería compatible con la eficiencia de la metaprogramación de plantillas)


Aprecio el pensamiento que Yakk puso en los tipos de retorno para que no tuviera que hacerlo, pero se vuelve mucho más simple:

template<typename T> T&& vmin(T&& val) { return std::forward<T>(val); } template<typename T0, typename T1, typename... Ts> auto vmin(T0&& val1, T1&& val2, Ts&&... vs) { return (val1 < val2) ? vmin(val1, std::forward<Ts>(vs)...) : vmin(val2, std::forward<Ts>(vs)...); }

La deducción del tipo de devolución es bastante impresionante (puede requerir C ++ 14).


No puede enlazar una referencia temporal a una no constante, es por eso que probablemente obtenga el error de compilación. Es decir, en vmin(3, 2, 1, 2, 5) , los parámetros son temporales. Funcionará si los declara como por ejemplo int first=3,second=2 y así sucesivamente, luego invoque vmin(first,second...)


ejemplo vivo

Esto hace perfecto reenvío de argumentos. Se basa en RVO para valores de retorno, ya que devuelve un tipo de valor independientemente de los tipos de entrada, porque common_type hace eso.

Implementé la deducción de tipo common_type , permitiendo que se pasaran tipos mixtos y la salida del tipo de resultado "esperado".

Apoyamos el elemento mínimo de 1, porque hace que el código sea más pulido.

#include <utility> #include <type_traits> template<typename T> T vmin(T&&t) { return std::forward<T>(t); } template<typename T0, typename T1, typename... Ts> typename std::common_type< T0, T1, Ts... >::type vmin(T0&& val1, T1&& val2, Ts&&... vs) { if (val2 < val1) return vmin(val2, std::forward<Ts>(vs)...); else return vmin(val1, std::forward<Ts>(vs)...); } int main() { std::cout << vmin(3, 2, 0.9, 2, 5) << std::endl; std::cout << vmin(3., 1.2, 1.3, 2., 5.2) << std::endl; return 0; }

Ahora, mientras que lo anterior es una solución perfectamente aceptable, no es ideal.

La expresión ((a<b)?a:b) = 7 es C ++ legal, pero vmin( a, b ) = 7 no lo es, porque std::common_type decay s son argumentos a ciegas (causados ​​por lo que considero un over- reaccione a él devolviendo referencias de valor cuando se introducen dos tipos de valores en una implementación anterior de std::common_type ).

Simplemente usar decltype( true?a:b ) es tentador, pero da como resultado el problema de referencia de rvalue y no es compatible common_type especializaciones common_type (como ejemplo, std::chrono ). Así que ambos queremos usar common_type y no queremos usarlo.

En segundo lugar, escribir una función min que no admita punteros no relacionados y que no permita que el usuario cambie la función de comparación parece incorrecta.

Entonces, lo que sigue es una versión más compleja de lo anterior. ejemplo vivo :

#include <iostream> #include <utility> #include <type_traits> namespace my_min { // a common_type that when fed lvalue references all of the same type, returns an lvalue reference all of the same type // however, it is smart enough to also understand common_type specializations. This works around a quirk // in the standard, where (true?x:y) is an lvalue reference, while common_type< X, Y >::type is not. template<typename... Ts> struct my_common_type; template<typename T> struct my_common_type<T>{typedef T type;}; template<typename T0, typename T1, typename... Ts> struct my_common_type<T0, T1, Ts...> { typedef typename std::common_type<T0, T1>::type std_type; // if the types are the same, don''t change them, unlike what common_type does: typedef typename std::conditional< std::is_same< T0, T1 >::value, T0, std_type >::type working_type; // Careful! We do NOT want to return an rvalue reference. Just return T: typedef typename std::conditional< std::is_rvalue_reference< working_type >::value, typename std::decay< working_type >::type, working_type >::type common_type_for_first_two; // TODO: what about Base& and Derived&? Returning a Base& might be the right thing to do. // on the other hand, that encourages silent slicing. So maybe not. typedef typename my_common_type< common_type_for_first_two, Ts... >::type type; }; template<typename... Ts> using my_common_type_t = typename my_common_type<Ts...>::type; // not that this returns a value type if t is an rvalue: template<typename Picker, typename T> T pick(Picker&& /*unused*/, T&&t) { return std::forward<T>(t); } // slight optimization would be to make Picker be forward-called at the actual 2-arg case, but I don''t care: template<typename Picker, typename T0, typename T1, typename... Ts> my_common_type_t< T0, T1, Ts...> pick(Picker&& picker, T0&& val1, T1&& val2, Ts&&... vs) { // if picker doesn''t prefer 2 over 1, use 1 -- stability! if (picker(val2, val1)) return pick(std::forward<Picker>(pick), val2, std::forward<Ts>(vs)...); else return pick(std::forward<Picker>(pick), val1, std::forward<Ts>(vs)...); } // possibly replace with less<void> in C++1y? struct lesser { template<typename LHS, typename RHS> bool operator()( LHS&& lhs, RHS&& rhs ) const { return std::less< typename std::decay<my_common_type_t<LHS, RHS>>::type >()( std::forward<LHS>(lhs), std::forward<RHS>(rhs) ); } }; // simply forward to the picked_min function with a smart less than functor // note that we support unrelated pointers! template<typename... Ts> auto min( Ts&&... ts )->decltype( pick( lesser(), std::declval<Ts>()... ) ) { return pick( lesser(), std::forward<Ts>(ts)... ); } } int main() { int x = 7; int y = 3; int z = -1; my_min::min(x, y, z) = 2; std::cout << x << "," << y << "," << z << "/n"; std::cout << my_min::min(3, 2, 0.9, 2, 5) << std::endl; std::cout << my_min::min(3., 1.2, 1.3, 2., 5.2) << std::endl; return 0; }

La desventaja de la implementación anterior es que la mayoría de las clases no son compatibles con operator=(T const&)&&=delete , es decir, no bloquean la asignación de valores, lo que puede llevar a sorpresas si uno de los tipos en el min sí lo hace. no Los tipos fundamentales hacen.

Que es una nota al margen: comience a eliminar las personas de su operator= referencia de rvalor.