lundi 21 août 2023

Emulating lambdas in C++03 for flow-control purposes in macros

I have some existing code in a header file which needs to be useable in the context of C++03 & C++11

It defines a macro TABORT that takes in a printf-style format string & arguments to be formatted using that string, prints the result to stdout, and then calls std::abort

Essentially, something similar to

#define TABORT(...) do { fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); fprintf(stderr, __VA_ARGS__); std::abort(); } while(0)

I wanted to add some logic to catch the case where evaluating __VA_ARGS__ would throw an exception, preventing the call to std::abort. For example, in the following:

if (SomethingReallyBadHappened())
    TABORT("Aborting because %s", GetReallyBadDetails().c_str());

if GetReallyBadDetails throws an exception, I want to ensure that abort gets called (here, not after some exception unwinding).

So, I did something like:

#define TABORT(...) do { fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__); try { fprintf(stderr, __VA_ARGS__); } catch (...) { fprintf(stderr, "<Failed to evaluate abort message>\n"); } std::abort(); } while(0)

But this is causing the C4714 warning in visual studio when the macro is used in functions marked __forceinline, probably due to

In some cases, the compiler will not inline a particular function for mechanical reasons. For example, the compiler will not inline:

  • A function with a try (C++ exception handling) statement.

So, to avoid that warning (& to keep inlining functions which were previously determined to need inlining inlined [I hope this was done alongside profiling...]), I was thinking to do something like

// In header

// Assume this is like std::function except construction from a lambda can't throw
template <typename Sig>
class function_ref;
using PrintAbortMsg = function_ref<void(const char*)>;
void [[noreturn]] do_tabort(const char* file, int line, function_ref<void(PrintAbortMsg &)> msgGenerator) noexcept;
#define TABORT(...) do_tabort(__FILE__, __LINE__, [&](PrintAbortMsg& print) { print(SprintfToStdString(__VA_ARGS__).c_str()); })

// In cpp

void do_tabort(const char* file, int line, function_ref<void(PrintAbortMsg&)> msgGenerator) noexcept
{
    fprintf(stderr, "Aborting at %s:%d for reason: ", __FILE__, __LINE__);
    try
    {
        msgGenerator([](const char* msg) { fprintf(stderr, "%s\n", msg); });
    }
    catch (...)
    {
        fprintf(stderr, "<Failed to evaluate abort message>\n");
    }
    std::abort();
}

Which I think would work in the context of C++11, but I'm not sure how to do something which will work for for C++03. There are existing usages of the macro which I don't want to touch.

Aucun commentaire:

Enregistrer un commentaire