Search code examples
c++exceptionsubclassc++-modules

Error with custom exception in C++ module (conflicting definition of __cxa_throw)


I get the following error when creating a custom exception in a C++ module:

C:/Sync/Engine/Chorus/Base/win_9x.cpp:600:59: error: conflicting declaration of C function 'void __cxa_throw(void*, void*, void (__attribute__((thiscall)) *)(void*))'
  600 |                 throw Crash(Win9xErrorCode::BAD_MAIN_CLASS);
      |                                                           ^
In file included from C:/msys64/mingw32/include/c++/14.1.0/bits/version.h:49,
                 from C:/msys64/mingw32/include/c++/14.1.0/variant:36,
of module C:/msys64/mingw32/include/c++/14.1.0/variant, imported at C:/Sync/Engine/Chorus/Base/error.cppm:16,
of module Error, imported at C:/Sync/Engine/Chorus/Base/win_9x.cpp:217:
C:/msys64/mingw32/include/c++/14.1.0/variant:1349:5: note: previous declaration 'void __cxa_throw(void*, void*, void (*)(void*))'
 1349 |   { _GLIBCXX_THROW_OR_ABORT(bad_variant_access(__what)); }
      |     ^~~~~~~~~~~~~~~~~~~~~~~

I'm using the MSYS version of G++, version 14.1.0. Is this a known (unimplemented) feature, or could it be something in my code? Here's a simplified version of the declaration (... is snipped code):

export module Error;

...

enum class ErrorType: uint8_t {
    ...
};
export enum class Win9xErrorCode: uint16_t {
    ...
};
export enum class WinXPErrorCode: uint16_t {
    ...
};

export class Crash: public std::exception {
    public:
        Crash(Win9xErrorCode crashCode) noexcept: code(crashCode), type(ErrorType::WIN9X) { }
        Crash(WinXPErrorCode crashCode) noexcept: code(crashCode), type(ErrorType::WINXP) { }

    private:
        std::variant<Win9xErrorCode, WinXPErrorCode, Win7ErrorCode> code;
        ErrorType type;
};

For reference, the code that trips is not in a module:

void setupMainWindow(...) {
    WNDCLASSEX graphicsWindowClass = {
        ...
    };
    if(!RegisterClassEx(&graphicsWindowClass)) {
        throw Crash(Win9xErrorCode::BAD_MAIN_CLASS);
    }
}

Solution

  • This appears to be a bug in GCC, possibly related to (or caused by) Bug 114968 - [14/15 Regression] missing __thiscall attribute on builtin declaration of __cxa_thread_atexit() (which also affects __cxa_throw). This bug specifically affects mingw for 32-bit Intel targets.

    One declaration of __cxa_throw is coming from a throw in <variant>, and looks like void __cxa_throw(void*, void*, void (*)(void*)). The other declaration of __cxa_throw is coming from your module, and looks like void __cxa_throw(void*, void*, void (__attribute__((thiscall)) *)(void*)). The only difference is that the destructor callback (third argument) is declared thiscall in the module.

    If you're using a version of GCC that predates the fix to that bug, I'd try upgrading. If you're using a version of GCC that already incorporates that fix, it's probable that the fix didn't solve the problem entirely, which would warrant a new bug report.