Подтвердить что ты не робот

Почему стандарт позволяет кортежу ссылок на rvalue присваиваться кортежем lvalue-ссылок?

Похоже, что std::tuple, содержащий одну или несколько ссылок, имеет непредвиденное поведение в отношении построения и назначения (особенно для копирования/перемещения и копирования/перемещения). Он отличается от поведения как std::reference_wrapper (меняет связанный объект), так и структуры с ссылочной переменной-членом (оператор присваивания удален). Это позволяет использовать удобный std::tie python как несколько возвращаемых значений, но также позволяет явно неправильный код, например, (ссылка здесь):

#include <tuple>

int main()
{
    std::tuple<int&> x{std::forward_as_tuple(9)}; // OK - doesn't seem like it should be
    std::forward_as_tuple(5) = x; // OK - doesn't seem like it should be
    // std::get<0>(std::forward_as_tuple(5)) = std::get<0>(x); // ERROR - and should be
    return 0;
}

Стандарт, по-видимому, требует или сильно намекает на это поведение в разделе назначения последнего рабочего проекта (Ti& будет сбрасываться до lvalue ref):

template <class... UTypes> tuple& operator=(const tuple<UTypes...>& u);

9 Требуется: sizeof...(Types) == sizeof...(UTypes) и is_assignable<Ti&, const Ui&>::value true для всех i.

10 Эффекты: Назначает каждому элементу u соответствующий элемент * этого.

11 Возвращает: * this

Хотя секция построения движения (ish) 20.4.2.1.20 менее понятна (is_constructible<int&, int&&> возвращает false):

template <class... UTypes> EXPLICIT constexpr tuple(tuple<UTypes...>&& u);

18 Требуется: sizeof...(Types) == sizeof...(UTypes).

19 Эффекты: Для всех i конструктор инициализирует i -й элемент *this с помощью std::forward<Ui>(get<i>(u)).

20 Примечания: Этот конструктор не должен участвовать в разрешении перегрузки, если только is_constructible<Ti, Ui&&>::value не подходит для всех i. Конструктор explicit тогда и только тогда, когда is_convertible<Ui&&, Ti>::value false для хотя бы одного i.

Это не единственные затронутые подразделы.

Вопрос в том, почему это поведение желательно? Кроме того, если в игре есть другие части стандарта, или я не понимаю, объясните, где я ошибся.

Спасибо!

4b9b3361

Ответ 1

Конструктор

Начните с конструктора (ваша первая строка):

Как мы знаем (просто уточняя), decltype(std::forward_as_tuple(9)) есть std::tuple<int&&>.

Также обратите внимание:

std::is_constructible<int&, int&&>::value == false

на всех платформах компилятора. Поэтому, согласно спецификации, которую вы цитируете, что соответствует последнему рабочему проекту С++ 1z, конструкция в вашей первой строке кода не должна участвовать в разрешении перегрузки.

В clang с libС++ он правильно не компилируется в соответствии с этой спецификацией:

http://melpon.org/wandbox/permlink/7cTyS3luVn1XRXGv

С последним gcc он неправильно компилируется в соответствии с этой спецификацией:

http://melpon.org/wandbox/permlink/CSoB1BTNF3emIuvm

И с последним VS-2015 он неправильно компилируется в соответствии с этой спецификацией:

http://webcompiler.cloudapp.net

(эта последняя ссылка не содержит правильный код, вы должны вставить ее).

В вашей ссылке coliru, хотя вы используете clang, clang неявно использует gcc libstdС++ для std:: lib, поэтому ваши результаты согласуются с моими.

Из этого опроса выяснилось, что только libС++ согласуется как со спецификацией, так и с вашими ожиданиями. Но это еще не конец истории.

Последняя рабочая спецификация, которую вы правильно указали, отличается от спецификации С++ 14, которая выглядит так:

template <class... UTypes> constexpr tuple(tuple<UTypes...>&& u);

Требуется: sizeof...(Types) == sizeof...(Types). is_constructible<Ti, Ui&&>::value true для всех i.

Эффекты: для всех я инициализирует i-й элемент *this с std::forward<Ui>(get<i>(u)).

Примечание. Этот конструктор не должен участвовать в разрешении перегрузки если каждый тип в UTypes неявно конвертируется в его соответствующий тип в Types.

Это было N4387, которое изменило спецификацию после С++ 14.

В С++ 14 (а также в С++ 11) на клиенте находится клиент, чтобы is_constructible<Ti, Ui&&>::value был true для всех i. И если это не так, у вас есть поведение undefined.

Итак, ваша первая строка кода - это поведение undefined в С++ 11 и С++ 14 и плохо сформированное в рабочем проекте С++ 1z. С поведением undefined все может случиться. Таким образом, все libС++, libstdС++ и VS соответствуют требованиям С++ 11 и С++ 14.


Обновить, вдохновленный T.C. ниже:

Обратите внимание, что удаление этого одного конструктора из разрешения перегрузки может позволить выражению использовать другой конструктор, такой как tuple(const tuple<UTypes...>& u). Однако этот конструктор также удаляется из разрешения перегрузки с помощью аналогичного предложения Notes.

std::is_constructible<int&, const int&>::value == false

Увы, T.C. возможно, обнаружили дефект в рабочем проекте. Фактическая спецификация говорит:

std::is_constructible<int&, int&>::value == true

поскольку для ссылки используется const, а не int.


Похоже, что libstdС++ и VS еще не реализовали спецификацию post-С++ 14, указанную N4387, и которая в значительной степени указывает, что должно указывать ваши комментарии. libС++ много лет реализовывал N4387 и служил доказательством реализации этого предложения.

Назначение

Для этого требуется:

std::is_assignable<int&, const int&>::value == true

и это действительно так в вашем примере. В этой строке кода. Однако я думаю, что можно было бы аргументировать, что вместо этого ему потребуется:

std::is_assignable<int&&, const int&>::value == true

который является ложным. Теперь, если мы сделаем это изменение, ваше назначение будет undefined, поскольку эта часть спецификации находится в предложении Requires. Поэтому, если вы действительно хотите сделать это правильно, вам нужно переместить spec в предложение Remarks с рецептом "не участвует в разрешении перегрузки". И тогда он будет вести себя так, как ваши комментарии показывают, что вы ожидаете этого. Я бы поддержал такое предложение. Но вам придется это сделать, не спрашивайте меня. Но я предлагаю вам помочь в этом, если хотите.