Lambdas C++ 11 passés en tant que paramètres std :: function - répartition sur le type de retour

StackOverflow https://stackoverflow.com//questions/9688767

Question

Le scénario:

Nous avons une API dotée d'une interface générique de gestion des erreurs.Il s'agit d'une API C, elle nous indique donc qu'après chaque appel de fonction API, nous devons exécuter un passe-partout comme celui-ci :

if (APIerrorHappened()) {
    APIHandle h;
    while(h = APIgetNextErrorContext(...)) {
       cerr << APIgetText(h) << endl;
    }
}

Vous détestez évidemment vous répéter, vous souhaitez donc encapsuler cette manipulation dans une macro qui vous permet d'écrire du code comme celui-ci :

...
// this API call returns something
APItype foo = MYMACRO(APIsomeFunction1(...));
// this one doesn't
MYMACRO(APIsomeFunction2(...));
// neither does this (glCompileShader)
MYMACRO(APIsomeFunction3(...));
...

Vous pouvez également penser à cela en termes de programmation orientée aspect - imaginez que la macro ajoute une journalisation, envoie des informations au moniteur distant, peu importe...Le fait est qu'il est censé encapsuler une expression, faire ce que vous voulez autour et renvoie le type renvoyé par l'expression - et bien sûr, l'expression ne peut pas renvoyer rien.

Ce qui veut dire que tu ne peux pas simplement faire

#define MYMACRO(x) { auto x = expr(); ... }

...car dans certains cas, l'expression ne renvoie rien !

Donc...Comment feriez-vous cela?

S'il vous plaît, ne suggérez pas d'encapsuler l'instruction complète dans la macro...

#define MYMACRO(x)       \
{                        \
   /* ... stuff ...   */ \
   x;                    \
   // ... stuff
}

... puisque cela ne fonctionnerait jamais pour des choses comme :

if (foo() || APIfunctionReturningBool(...) || bar()) {
     ...
     APIfunction1();
     ...
} else if (APIOtherfunctionReturningBool() || baz()) {
     ...
     APIfunction2();
     ...
}

...vous engloutissez toute l'instruction if ?Ses actions incluent d'autres appels API, donc...une macro dans une macro ?Le débogage est devenu un enfer.

Ma propre tentative est ci-dessous, en utilisant lambdas et std::function - mais c'est sans doute moche...Je n'ai pas pu transmettre un lambda de l'expression directement à un modèle qui prend std::function (pour se spécialiser en fonction du type de retour du lambda), donc le code s'est avéré plutôt méchant.

Pouvez-vous penser à une meilleure façon ?

void commonCode(const char *file, size_t lineno) {
    // ... error handling boilerplate
    // ... that reports file and lineno of error
}

template <class T>
auto MyAPIError(std::function<T()>&& t, const char *file, size_t lineno) -> decltype(t()) {
    auto ret = t();
    commonCode(file,lineno);
    return ret;
}

template<>
void MyAPIError(std::function<void(void)>&& t, const char *file, size_t lineno) {
    t();
    commonCode(file,lineno);
}

template <class T>
auto helper (T&& t) -> std::function<decltype(t())()>
{
    std::function<decltype(t())()> tmp = t;
    return tmp;
}

#define APIERROR( expr ) \
    return MyAPIError( helper( [&]() { return expr; } ),  __FILE__, __LINE__);

UPDATE, un addendum à l'excellente solution de KennyTM

J'ai placé le code OpenGL qui a déclenché cette question ici.Comme vous pouvez le voir, le code de vérification des erreurs a fait plus que simplement imprimer : il a également levé une exception que le code utilisateur pourrait ensuite gérer.J'ajoute cet addendum pour noter qu'avec la solution de KennyTM, vous finissez par lever cette exception depuis un destructeur, et que c'est OK (lire la suite) :

struct ErrorChecker { 
    const char *file; 
    size_t lineno; 
    ~ErrorChecker() { 
        GLenum err = glGetError(); 
        if (err != GL_NO_ERROR) { 
            while (err != GL_NO_ERROR) { 
                std::cerr <<  
                    "glError: " << (char *)gluErrorString(err) <<  
                    " (" << file << ":" << lineno << ")" << std::endl; 
                err = glGetError(); 
            } 
            throw "Failure in GLSL..."; 
        } 
    } 
};

La raison pour laquelle il est acceptable de lancer depuis ce destructeur est expliquée dans le FAQ C++:

La règle C++ est que vous ne devez jamais lever d'exception à partir d'un destructeur appelé pendant le processus de « déroulement de la pile » d'une autre exception...vous pouvez dire de ne jamais lancer d'exception à partir d'un destructeur lors du traitement d'une autre exception.

Dans notre cas, nous voulons que le code utilisateur (qui appelle la macro spéciale) gère l'exception ;nous devons donc être sûrs que notre "lancement" dans le destructeur d'ErrorChecker est le premier - c'est-à-direque l'API C réelle appelée ne pourra jamais lancer.Cela se réalise facilement avec ce formulaire :

#define GLERROR(...)                                    \
    ([&]() -> decltype(__VA_ARGS__)                     \
    {                                                   \
        ErrorChecker _api_checker {__FILE__, __LINE__}; \
        (void) _api_checker;                            \
        try {                                           \
            return __VA_ARGS__;                         \
        } catch(...) {}                                 \
    } ())

Cette forme de macro garantit que l'API C réelle (appelée via VA_ARGS) ne lancera jamais - et par conséquent, le "lancer" dans le destructeur d'ErrorChecker sera toujours le Premier je le fais.

Cette solution couvre donc tous les angles de ma question initiale - merci beaucoup à Alexandre Turner pour l'avoir fourni.

Était-ce utile?

La solution

Placez le code de journalisation dans un destructeur d'une classe - en supposant que l'enregistreur ne lancera pas - puis créez une instance de cette classe dans la macro.En abusant de l'opérateur virgule, nous avons :

struct Logger
{
    const char* file;
    int lineno;
    ~Logger()
    {
        // The APIerrorHappened stuff.
    }
};

#define APIERROR(...) (Logger{__FILE__, __LINE__}, (__VA_ARGS__))

Démo : http://ideone.com/CiLGR

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top