Привязка lvalue к rvalue reference - g++ ошибка?

В качестве ответа на еще один вопрос я хотел опубликовать следующий код (то есть, я хотел бы опубликовать код на основе этой идеи):

#include <iostream>
#include <utility>      // std::is_same, std::enable_if
using namespace std;

template< class Type >
struct Boxed
{
    Type value;

    template< class Arg >
    Boxed(
        Arg const& v,
        typename enable_if< is_same< Type, Arg >::value, Arg >::type* = 0
        )
        : value( v )
    {
        wcout << "Generic!" << endl;
    }

    Boxed( Type&& v ): value( move( v ) )
    {
        wcout << "Rvalue!" << endl;
    }
};

void function( Boxed< int > v ) {}

int main()
{
    int i = 5;
    function( i );  //<- this is acceptable

    char c = 'a';
    function( c );  //<- I would NOT like this to compile
}

Однако, хотя MSVC 11.0 дросселируется при последнем вызове, так как он должен IHMO, MinGW g++ 4.7.1 просто принимает его и вызывает конструктор с формальным аргументом rvalue reference.

Мне кажется, что lvalue привязана к ссылке rvalue. Ответ glib может состоять в том, что lvalue преобразуется в rvalue. Но вопрос в том, является ли это ошибкой компилятора, и если это не так, как это разрешено Священным стандартом?


EDIT: мне удалось свести все это к следующему довольно короткому примеру:

void foo( double&& ) {}

int main()
{
    char ch = '!';
    foo( ch );
}

Не удается скомпилировать с MSVC 11.0, компилируется ли с MinGW 4.7.1, что правильно?

Ответ 1

Предположительно вы согласны, что это действительно?

void foo( double ) {}  // pass-by-value

int main()
{
    char ch = '!';
    foo( ch );
}

Там подразумевается преобразование от char до double, поэтому функция жизнеспособна.

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

void foo( double&& ) {}  // pass-by-reference

int main()
{
    char ch = '!';
    foo( double(ch) );
}

но в этом случае это ничего не изменит. Это было бы необходимо, если doublechar можно было бы явно преобразовать (например, для типов классов с явными конструкторами или явными операторами преобразования), но double to char является допустимым неявным преобразованием.

"Правило rvalue-reference не может привязываться к правилу lvalue", о котором вы думаете, относится к привязке T&& к значению T lvalue, и это правило не нарушается, потому что double&& не привязать к char, он связывается с временным, созданным неявным преобразованием.

Это правило существует не только для предотвращения ненужного дополнительного копирования, но и для устранения реальной проблемы безопасности, существовавшей с предыдущими правилами, см. http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2812.html

Изменить:. Было задано, желательно ли это поведение на рефлекторе комитета (см. DR 1414), и это было решено, что да, это поведение предназначено и является правильным. Один из аргументов, используемых для достижения этой позиции, заключался в том, что этот код более эффективен с текущими правилами:

std::vector<std::string> v;
v.push_back("text");

При существующих правилах временный std::string создается неявным преобразованием, тогда вызывается std::vector<T>::push_back(T&&), а временное перемещается в вектор. Если эта перегрузка push_back не была жизнеспособной для результата преобразования, тогда указанный выше код вызывал бы std::vector<T>::push_back(const T&), который вызывал бы копию. Существующие правила делают этот реальный прецедент более эффективным. Если правила говорят, что rvalue-refs не могут связываться с результатом неявных преобразований, вам нужно будет изменить код выше, чтобы получить эффективность перемещения:

v.push_back( std::string{"text"} );

IMHO нет смысла явно строить a std::string, когда этот конструктор не является явным. Я хочу последовательное поведение от явных/неявных конструкторов, и я хочу, чтобы первый пример push_back был более эффективным.

Ответ 2

Я не проверяю спецификацию, но, думаю, char можно автоматически добавить в int. Поскольку вы не можете назначить что-либо (это значение r), будет передано значение R для временной переменной типа int (чтобы быть более явным для значения (int)c).

Ответ 3

Я обнаружил, что N3290 (идентичный стандарту С++ 11) содержит ненормативный пример привязки double&& к rvalue, сгенерированный из int lvalue, и обновленная формулировка в §8.5.3

"Если T1 относится к T2, а ссылка является ссылкой rvalue, выражение инициализатора не должно быть lvalue."

Как сообщается, правила были разработаны, чтобы избежать неэффективного дополнительного копирования. Хотя я не вижу, как такое копирование невозможно оптимизировать. Во всяком случае, разумно ли обоснование или нет? и это, конечно, не кажется разумным эффектом! – разрешен следующий код и компилируется как с MSVC 11, так и с MinGW g++ 4.7:

struct Foo {};
struct Bar { Bar( Foo ) {} };

void ugh( Bar&& ) {}

int main()
{
    Foo o;
    ugh( o );
}

Таким образом, очевидно, что MSVC 11 ошибочен, если не разрешить преобразование lvalue → rvalue.


РЕДАКТИРОВАТЬ. Я узнал, что есть отчет о дефектах по этой проблеме, DR 1414. Вывод в феврале 2012 года заключался в том, что текущая спецификация поведения "правильная", по-видимому, в отношении того, насколько хорошо она отражает намерение. Однако, как сообщается, он по-прежнему обсуждается в комитете, предположительно, в отношении практичности намерения.