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 se puede garantizar el orden de evaluación de los argumentos para los constructores: Usando una lista de inicios arriostrados el orden está garantizado 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 una lista de argumentos para llamar a la función con un orden definido de evaluación de sus argumentos. La pregunta es: ¿se pueden usar las garantías para los constructores para construir un recurso de llamada a función (” function_apply()
“) con una garantía de pedido para la evaluación de argumentos? Es aceptable solicitar que se llame a un objeto de función.
¿Qué tal una clase tonta de envoltura como esta?
struct OrderedCall { template OrderedCall(F && f, Args &&... args) { std::forward(f)(std::forward(args)...); } };
Uso:
void foo(int, char, bool); OrderedCall{foo, 5, 'x', false};
Si desea un valor devuelto, puede pasarlo por referencia (necesitará algún rasgo para extraer el tipo de devolución), o almacenarlo en el objeto, para obtener una interfaz como:
auto x = OrderedCall{foo, 5, 'x', false}.get_result();
La solución que he encontrado utiliza std::tuple<...>
para unir 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 devolución. La lógica específica real se ve así:
template auto function_apply(F&& f, T&& t, indices const*) -> decltype(f(std::get(t)...)) { f(std::get(t)...); } template auto function_apply(F&& f, T&& t) -> decltype(function_apply(std::forward(f), std::forward(t), make_indices ())) { function_apply(std::forward(f), std::forward(t), make_indices ()); }
… 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{ fi(), fd(), fb() }); }
La principal desventaja es que este enfoque requiere la especificación de los elementos de std::tuple<...>
. Otro problema es que la versión actual de gcc en MacOS llama a las funciones en el orden opuesto en que aparecen, es decir, el orden de evaluación en una lista de inicio no se obedece (un error de gcc) o no existe (es decir, , Entendí mal las garantías de usar una lista de inicio reforzada. El 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
con una lista de índices utilizables con std::tuple<...>
:
template struct indices; template <> struct indices<-1> { typedef indices<> type; }; template struct indices<0, Indices...> { typedef indices<0, Indices...> type; }; template struct indices { typedef typename indices::type type; }; template typename indices::value - 1>::type const* make_indices() { return 0; }
En primer lugar, creo que si el pedido importa, probablemente sea mejor construir explícitamente esos elementos antes de la llamada, y luego pasarlos. ¡Mucho más fácil de leer, pero menos divertido!
Esto solo se está expandiendo en la respuesta de Kerrek:
#include namespace detail { // the ultimate end result of the call; // replaceable with std::result_of? I do not know. template static auto ordered_call_result(F&& f, Args&&... args) -> decltype(std::forward(f) (std::forward(args)...)); // not defined template class ordered_call_helper { public: template ordered_call_helper(F&& f, Args&&... args) : mResult(std::forward(f)(std::forward(args)...)) {} operator R() { return std::move(mResult); } private: R mResult; }; template <> class ordered_call_helper { public: template ordered_call_helper(F&& f, Args&&... args) { std::forward(f)(std::forward(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(detail::ordered_call_helper {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 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() }; }
Esa última línea está allí para verificar que los inicializadores de refuerzo sí tienen efecto, normalmente.
Desafortunadamente, como se ha descubierto a partir de otras respuestas / comentarios, GCC no lo hace bien, así que no puedo probar mi respuesta. Además, MSVC Nov2012CTP tampoco lo hace bien (y tiene un error desagradable que ahoga en el ordered_call_result
†). Si alguien quiere probar esto con clang, eso sería genial.
† Para este ejemplo en particular, el tipo de retorno final puede ser decltype(f(0, 0, 0))
.
¿Se pueden usar las garantías para los constructores para construir un recurso de llamada de función (“function_apply ()”) con una garantía de pedido 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()
se llamará antes que bar()
.