¿Cómo garantizar el orden de evaluación de argumentos cuando se llama a un objeto de función?

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() .