Секвенирование разрушения параметров функции

Согласно С++ 14 [expr.call]/4:

Время жизни параметра заканчивается, когда возвращается функция, в которой она определена.

Это, по-видимому, означает, что деструктор параметра должен работать до того, как код, который вызвал функцию, продолжает использовать возвращаемое значение функции.

Однако этот код показывает иначе:

#include <iostream>

struct G
{
    G(int): moved(0) { std::cout << "G(int)\n"; }
    G(G&&): moved(1) { std::cout << "G(G&&)\n"; }
    ~G() { std::cout << (moved ? "~G(G&&)\n" : "~G()\n"); }

    int moved;
};

struct F
{
    F(int) { std::cout << "F(int)\n"; }
    ~F() { std::cout << "~F()\n"; }
};

int func(G gparm)
{
    std::cout << "---- In func.\n";
    return 0;
}


int main()
{
    F v { func(0) };
    std::cout << "---- End of main.\n";
    return 0;
}

Выход для gcc и clang с -fno-elide-constructors, (с моими аннотациями):

G(int)               // Temporary used to copy-initialize gparm
G(G&&)               // gparm
---- In func.
F(int)               // v
~G(G&&)              // gparm
~G()                 // Temporary used to copy-initialize gparm
---- End of main.
~F()                 // v

Итак, ясно, что конструктор v работает до деструктора gparm. Но в MSVC gparm уничтожается до запуска конструктора v.

Такую же проблему можно увидеть с включенным разрешением копирования и/или с помощью func({0}), чтобы параметр инициализировался напрямую. v всегда строится до того, как gparm будет разрушен. Я также заметил проблему в более длинной цепи, например. F v = f(g(h(i(j()))); не уничтожил ни одного из параметров f,g,h,i, пока не будет инициализирован v.

Это может быть проблемой на практике, например, если ~G разблокирует ресурс, а F() получает ресурс, это будет тупик. Или, если ~G выбрасывает, тогда выполнение должно перейти к обработчику catch без v, который был инициализирован.

Мой вопрос: разрешает ли стандарт оба этих заказа?, Есть ли более конкретное определение отношения секвенирования, связанное с разрушением параметров, чем просто цитата из expr.call/4, которая не использует стандартные термины секвенирования?

Ответы

Ответ 1

Собственно, я могу ответить на свой вопрос... не нашел ответа во время поиска, прежде чем писать его, но потом снова снова поискал ответ (типичный ха-ха).

В любом случае: эта проблема CWG # 1880 с разрешением:

Примечания к собранию в июне 2014 года:

WG решила не указывать, уничтожены ли объекты параметров сразу после вызова или в конце полного выражения, которому принадлежит вызов.

Последний черновик С++ 17, который у меня есть (N4606), изменил текст в [expr.call]/4:

Определяется реализацией, заканчивается ли время жизни параметра, когда функция, в которой она определена, возвращает или в конце охватывающего полного выражения.

Я думаю, мы должны рассматривать эту резолюцию (то есть "определенную реализацию" ) как применяемую ретроактивно, поскольку она не была хорошо указана опубликованными стандартами.

Примечание. Определение полного выражения можно найти в С++ 14 [intro.execution]/10:

Полное выражение - это выражение, которое не является подвыражением другого выражения. [...] Если языковая конструкция определена для получения неявного вызова функции, использование конструкции языка считается выражением для целей этого определения.

Итак, F v { func(0) }; является охватывающим полным выражением для gparm (хотя это объявление, а не выражение!).