Copy elision: переместить конструктор, не вызываемый при использовании тернарного выражения в return statement?

Рассмотрим следующий пример:

#include <cstdio>

class object
{
public:
    object()
    {
        printf("constructor\n");
    }

    object(const object &)
    {
        printf("copy constructor\n");
    }

    object(object &&)
    {
        printf("move constructor\n");
    }
};

static object create_object()
{
    object a;
    object b;

    volatile int i = 1;

// With #if 0, object copy constructor is called; otherwise, its move constructor.
#if 0
    if (i)
        return b; // moves because of the copy elision rules
    else
        return a; // moves because of the copy elision rules
#else
    // Seems equivalent to the above, but behaves differently.
    return i ? b : a; // copies (with g++ 4.7)
#endif
}

int main()
{
    auto data(create_object());

    return 0;
}

И рассмотрим этот бит из рабочего проекта С++ 11, n3337.pdf, 12.8 [class.copy], пункт 32:

Когда критерии для выполнения операции копирования выполняются или выполняются, за исключением того факта, что исходный объект является параметром функции, , а подлежащий копированию объект определяется значением lvalue, разрешением перегрузки для выбора конструктор для копии сначала выполняется так, как если бы объект был обозначен rvalue. Если сбой при перегрузке или если тип первого параметра выбранного конструктора не является ссылкой rvalue на тип объекта (возможно, с квалификацией cv), разрешение перегрузки выполняется снова, считая объект как lvalue. [Примечание. Это двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование. Он определяет вызывающий конструктор, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов отменяется. -end note]

Таким образом, если мы используем #if 1 в примере, сначала создается конструктор перемещения при возврате объекта, а затем конструктор копирования. Поскольку у нас есть конструктор перемещения, он используется вместо конструктора копирования.

В последнем выражении return в create_object(), однако, мы заметили, что конструктор перемещения не используется. Является ли это или не является нарушением языковых правил? Требует ли язык, чтобы конструктор перемещения использовался в последнем выражении return?

Ответ 1

Спецификация условного оператора настолько сложна, что это страшно. Но я считаю, что ваш компилятор прав в своем поведении. См. 5.16 [expr.cond]/p4:

Если второй и третий операнды являются значениями одного и того же значения категории и имеют один и тот же тип, результат такого типа и значения категория...

Также см. 12.8 [class.copy], p31, b1, который описывает, когда разрешено копирование:

в выражении return в функции с типом возвращаемого класса, когда выражение - это имя энергонезависимого автоматического объекта (другое чем параметр функции или catch-clause) с тем же cv- неквалифицированный тип в качестве возвращаемого типа функции, операция копирования/перемещения может быть опущен...

Выражение не является именем автоматического объекта, но является условным выражением (и это условное выражение является lvalue). Таким образом, копирование elision не допускается здесь, и нет ничего другого, говорящего о том, что выражение lvalue может притворяться rvalue для разрешения перегрузки.