Должны ли исключения, выбрасываемые из инициализаторов классов, вызвать std:: terminate()?

С учетом этого кода:

struct A {
    A(int e) { throw e; }
};

struct B {
    A a{42}; // Same with = 42; syntax
};

int main() {
    try {
        B b;
    } catch (int const e) {
        return e;
    }
}

При компиляции с GCC (версии 4.7.4, 4.8.5, 4.9.3, 5.4.0, 6.3.0):

$ g++ -std=c++11 test.cpp -o test; ./test ; echo $?
terminate called after throwing an instance of 'int'
Aborted
134

Но при компиляции с Clang (версия 4.0.0):

$ clang++ -std=c++11 test.cpp -o test; ./test ; echo $?
42

Какое поведение верно?

Ответ 1

Это ошибка в GCC (Ошибка 80683).

Если конструктор является первым оператором op в предложении try/catch, тогда компилятор считает его вне его, хотя он должен включать его.

Например, следующее работает просто отлично:

#include <iostream>

struct A {
    A(int e) { throw e; }
};

struct B {
    A a{42}; // Same with = 42; syntax
};

int main() {
    try {
        // The following forces the compiler to put B contructor inside the try/catch.
        std::cout << "Welcome" << std::endl; 
        B b;
    } catch (int e) {
        std::cout << "ERROR: " << e << std::endl; // This is just for debugging
    }

    return 0;
}

Продолжительность:

g++ -std=c++11 test.cpp -DNDEBUG -o test; ./test ; echo $?

Выходы:

Welcome
ERROR: 42
0

Я предполагаю, что из-за оптимизации компилятора он переводит конструктор в начало основной функции. Он предполагает, что struct B не имеет конструктора, тогда он предполагает, что он никогда не будет генерировать исключение, поэтому безопасно перемещать его за пределы предложения try/catch.

Если мы сменим объявление struct B на явно, используйте конструктор struct A:

struct B {
    B():a(42) {}
    A a;
};

Тогда результат будет таким, как ожидалось, и мы введем try/catch даже при удалении распечатки "Добро пожаловать":

ERROR: 42
0

Ответ 2

Кланг верен. Ссылки об этом можно найти в стандартном справочном проекте С++ n4296 (подчеркните мое):

15.3 Обработка исключения [except.handle]

...
3 Обработчик - это соответствие для объекта исключения типа E, если
(3.1) - Обработчик имеет тип cv T или cv T & и E и T одного типа (игнорирование cv-квалификаторов верхнего уровня),

Здесь вызывается int, а обработчик объявляет const int. Существует совпадение, и обработчик должен быть вызван.