una que punteros programacion parametros funciones funcion con amigas c++ c++11

que - punteros c++



¿Cómo garantizar el orden de evaluación de argumentos al llamar a un objeto de función? (4)

Las respuestas a la pregunta sobre cómo evitar el orden de ejecución indefinido para los constructores al usar std :: make_tuple llevaron a una discusión durante la cual aprendí que el orden de evaluación de los argumentos se puede garantizar para los constructores: usando una lista de inicio con braces, el orden Está garantizado para ser de izquierda a derecha:

T{ a, b, c }

Las expresiones a , b y c se evalúan en el orden dado. Este es el caso, incluso si el tipo T solo tiene un constructor normal definido.

Claramente, no todo lo que se llama es un constructor y, a veces, sería bueno garantizar el orden de evaluación cuando se llama a una función, pero no existe tal cosa como una lista de argumentos con llave para llamar a la función con un orden de evaluación definido de sus argumentos. La pregunta es: ¿Se pueden usar las garantías para los constructores para construir una función de llamada de función (" function_apply() ") con una garantía de orden para la evaluación de argumentos? Es aceptable requerir que se llame a un objeto de función.


¿Se pueden usar las garantías a los constructores para construir una función de llamada de función ("function_apply ()") con una garantía de orden para la evaluación de argumentos?

Sí, la biblioteca Fit ya lo hace con fit::apply_eval :

auto result = fit::apply_eval(f, [&]{ return foo() }, [&]{ return bar(); });

Así que foo() será llamado antes de la bar() .


¿Qué pasa con una clase de envoltura tonta como esta:

struct OrderedCall { template <typename F, typename ...Args> OrderedCall(F && f, Args &&... args) { std::forward<F>(f)(std::forward<Args>(args)...); } };

Uso:

void foo(int, char, bool); OrderedCall{foo, 5, ''x'', false};

Si desea un valor de retorno, puede pasarlo por referencia (necesitará algún rasgo para extraer el tipo de retorno), o almacenarlo en el objeto, para obtener una interfaz como:

auto x = OrderedCall{foo, 5, ''x'', false}.get_result();


La solución que encontré usa std::tuple<...> para juntar los argumentos que llama a un objeto de función utilizando los elementos de este objeto. La ventaja es que puede deducir el tipo de retorno. La lógica específica actual se ve así:

template <typename F, typename T, int... I> auto function_apply(F&& f, T&& t, indices<I...> const*) -> decltype(f(std::get<I>(t)...)) { f(std::get<I>(t)...); } template <typename F, typename T> auto function_apply(F&& f, T&& t) -> decltype(function_apply(std::forward<F>(f), std::forward<T>(t), make_indices<T>())) { function_apply(std::forward<F>(f), std::forward<T>(t), make_indices<T>()); }

... que se llama usando una expresión como esta:

void f(int i, double d, bool b) { std::cout << "i=" << i << " d=" << d << " b=" << b << ''/n''; } int fi() { std::cout << "int/n"; return 1; } double fd() { std::cout << "double/n"; return 2.1; } bool fb() { std::cout << "bool/n"; return true; } int main() { std::cout << std::boolalpha; function_apply(&f, std::tuple<int, double, bool>{ fi(), fd(), fb() }); }

La principal desventaja es que este enfoque requiere la especificación de los elementos std::tuple<...> . Otro problema es que la versión actual de gcc en MacOS llama a las funciones en el orden opuesto al que aparecen, es decir, el orden de evaluación en una lista de iniciados con braces no se cumple (un error de gcc) o no existe (es decir, , Entendí mal las garantías de usar una lista-init-list. Clang en la misma plataforma ejecuta las funciones en el orden esperado.

La función utilizada make_indices() simplemente crea un puntero adecuado para un objeto de tipo indices<I...> con una lista de índices utilizables con un std::tuple<...> :

template <int... Indices> struct indices; template <> struct indices<-1> { typedef indices<> type; }; template <int... Indices> struct indices<0, Indices...> { typedef indices<0, Indices...> type; }; template <int Index, int... Indices> struct indices<Index, Indices...> { typedef typename indices<Index - 1, Index, Indices...>::type type; }; template <typename T> typename indices<std::tuple_size<T>::value - 1>::type const* make_indices() { return 0; }


Primero, creo que si el orden importa, probablemente sea mejor construir explícitamente esos elementos antes de la llamada, luego pasarlos. ¡Mucho más fácil de leer, pero mucho menos divertido!

Esto es solo expandir la respuesta de Kerrek:

#include <utility> namespace detail { // the ultimate end result of the call; // replaceable with std::result_of? I do not know. template <typename F, typename... Args> static auto ordered_call_result(F&& f, Args&&... args) -> decltype(std::forward<F>(f) (std::forward<Args>(args)...)); // not defined template <typename R> class ordered_call_helper { public: template <typename F, typename... Args> ordered_call_helper(F&& f, Args&&... args) : mResult(std::forward<F>(f)(std::forward<Args>(args)...)) {} operator R() { return std::move(mResult); } private: R mResult; }; template <> class ordered_call_helper<void> { public: template <typename F, typename... Args> ordered_call_helper(F&& f, Args&&... args) { std::forward<F>(f)(std::forward<Args>(args)...); } }; // perform the call then coax out the result member via static_cast, // which also works nicely when the result type is void (doing nothing) #define ORDERED_CALL_DETAIL(r, f, ...) / static_cast<r>(detail::ordered_call_helper<r>{f, __VA_ARGS__}) }; // small level of indirection because we specify the result type twice #define ORDERED_CALL(f, ...) / ORDERED_CALL_DETAIL(decltype(detail::ordered_call_result(f, __VA_ARGS__)), / f, __VA_ARGS__)

Y un ejemplo:

#include <iostream> int add(int x, int y, int z) { return x + y + z; } void print(int x, int y, int z) { std::cout << "x: " << x << " y: " << y << " z: " << z << std::endl; } int get_x() { std::cout << "[x]"; return 11; } int get_y() { std::cout << "[y]"; return 16; } int get_z() { std::cout << "[z]"; return 12; } int main() { print(get_x(), get_y(), get_z()); std::cout << "sum: " << add(get_x(), get_y(), get_z()) << std::endl; std::cout << std::endl; ORDERED_CALL(print, get_x(), get_y(), get_z()); std::cout << "sum: " << ORDERED_CALL(add, get_x(), get_y(), get_z()) << std::endl; std::cout << std::endl; int verify[] = { get_x(), get_y(), get_z() }; }

La última línea está ahí para verificar que los inicializadores de llaves tengan efecto, de hecho, normalmente.

Desafortunadamente, como se ha descubierto en otras respuestas / comentarios, GCC no lo hace bien, por lo que no puedo probar mi respuesta. Además, MSVC Nov2012CTP tampoco lo hace bien (y tiene un error desagradable que se ahoga en el ordered_call_result †). Si alguien quiere probar esto con un sonido metálico, eso sería bueno.

† Para este ejemplo en particular, el tipo de retorno final puede ser decltype(f(0, 0, 0)) lugar.