¿Cuándo usar std::invoke en lugar de simplemente llamar al invocable?

3 minutos de lectura

Avatar de usuario de Elad Weiss
Elad Weiss

Según entiendo, std::invoke me permite hacer algo como:

std::invoke(f, arg1, arg2, ...);

¿Hay algún escenario en el que sea más ventajoso que simplemente hacer:

f(arg1, arg2, ...);

StoryTeller - Avatar de usuario de Unslander Monica
StoryTeller – Unslander Mónica

Si el invocable es un puntero a una función miembro, entonces debe hacer uno de estos:

(arg1->*f)(arg2,...);
(arg1.*f)(arg2,...);

Dependiendo de qué arg1 es.

INVOCAR (y su contraparte oficial de la biblioteca std::invoke) fue más o menos diseñado para simplificar tales líos.

usarías std::invoke para ayudar a la persona que llama a pasar su código ningún invocables, y no tener que adaptar su sitio de llamadas con una lambda o una llamada a std::bind.

  • Bonito, ya veo. Supongo que eso también significa que sin invocar necesitaré un código diferente para una función miembro y una función libre. ¿Está bien?

    – Elad Weiss

    24 de septiembre de 2017 a las 9:27

  • @EladWeiss – Sí. Tuvimos que escribir un montón de especializaciones o volver a implementar INVOKE nosotros mismos (e imaginar también admitir punteros inteligentes, yikes). La nueva utilidad es una herramienta de escritores de bibliotecas.

    – StoryTeller – Unslander Mónica

    24 de septiembre de 2017 a las 9:29


  • @EladWeiss Echa un vistazo a un implementación de muestra de std::invoke y luego agradezca que no tiene que escribir eso usted mismo: P

    – Rakete1111

    24 de septiembre de 2017 a las 9:30

  • @ Rakete1111 ahora imagina cómo se vería si if constexpr no estaba disponible.

    – Revólver_Ocelote

    24 de septiembre de 2017 a las 9:53

  • @ Rakete1111 Tenga en cuenta que hicimos trampa con esa implementación al asumir la disponibilidad de invoke_result_t y is_nothrow_invocable; el primero es particularmente crucial para hacer invoke Amigable con SFINAE. En la práctica, es probable que funcionen con la misma maquinaria subyacente, que debería ser más compleja. @StoryTeller correcto.

    – CT

    24 de septiembre de 2017 a las 10:28


std::invoke puede ser útil cuando crea una lambda y necesita llamarla de inmediato. Si la lambda es grande, los paréntesis después pueden ser difíciles de observar:

[] (/* args */) {
    // many lines here
    // ...
} (/* args */)

contra

std::invoke(
    [] (/* args */) {
        // many lines here
        // ...
    },
    /* args */);

Tratando de agregar además de las dos respuestas, una está dando una buena explicación teórica y la otra está tratando de proporcionar un ejemplo concreto. Aquí hay una buena razón de por qué std::invoke hace las cosas mejor.

#include <functional>
#include <iostream>

struct foo
{
    void echo(){std::cout << "foo" << std::endl;};
};

int main()
{
    ////
    // Vanilla version of invoking .echo()
    foo(f);
    f.echo();

    ////
    // Pointer to *class* member function version
    void (foo::* pf)() =  &foo::echo;
    foo obj;
    
    (obj.*pf)();    // ugly
    // instead do ...
    std::invoke(pf, obj);

    // obj->echo(); <-- does not compile

    return 0;
}

  • Ahora, la pregunta de por qué usaría/necesitaría un puntero a una función de miembro de clase es un tema diferente.

    – gonidelis

    7 jun a las 18:19


#include <iostream>
#include <functional>
template< class Callable, typename ... Args>{}

    //f(args...);   // if arg1 is a class object pointer 
                    // we should use it like this(arg1.*f)(args...); 

    std::invoke(f, args...); //  now  every thing is ok
}

void foo(char c) {
    std::cout << "foo called\n";
}

int main()
{
    struct S {
        int f1(char c) {
            std::cout << "S::f1 called\n";
        }
        void operator()(char c) {
            std::cout << "S::operator() called\n";
        }
    };
    int (S:: * fptr)(char c) = &S::f1;
    S  obj;

    dosomething(fptr, obj, 'a');
    return 0;
}

¿Ha sido útil esta solución?