Как будут взаимодействовать концепции с универсальными ссылками?

Недавно я просмотрел это видео, объяснив идеи концепций lite в С++, которые, вероятно, появятся в этом году как TS. Теперь я также узнал о ссылках на универсальные ссылки/пересылки (как описано здесь) и что T && & может иметь два значения в зависимости от контекста (т.е. если выполняется дедукция типа или нет). Это естественно приводит к вопросу о том, как концепции будут взаимодействовать с универсальными ссылками?

Чтобы сделать это конкретным, в следующем примере мы имеем

void f(int&& i) {}

int i = 0;
f(i);    // error, looks for f(int&)
f(0);    // fine, calls f(int&&)

и

template <typename T>
void f(T&& test) {}

int i = 0;
f(i);    // fine, calls f(T&&) with T = int& (int& && = int&)
f(0);    // fine, calls f(T&&) with T = int&& (int&& && = int&&)

Но что произойдет, если мы будем использовать понятия?

template <typename T>
    requires Number<T>
void f(T&& test) {}

template <Number T>
void g(T&& test) {}

void h(Number&& test) {}

int i = 0;
f(i);    // probably should be fine?
f(0);    // should be fine anyway
g(i);    // probably also fine?
g(0);    // fine anyway
h(i);    // fine or not?
h(0);    // fine anyway

Особенно последний пример меня немного беспокоит, так как есть два противоречивых принципы. Во-первых, предполагается, что концепция, используемая таким образом, работает как тип, а вторая, если T - выведенный тип, T && обозначает универсальную ссылку вместо ссылки rvalue.

Заранее благодарим за это!

Ответ 1

Все зависит от того, как написано само понятие. Concepts-Lite (последние TS на момент написания этой статьи) агностик в этом вопросе: он определяет механизмы, с помощью которых концепции могут быть определены и использованы в язык, но не добавляет концепций акций в библиотеку.

С другой стороны, документ N4263 К стандартной библиотеке, включенной в концепцию, является выражение о намерениях некоторых членов Стандартного комитета, в котором предлагается естественный шаг после того, как Concepts-Lite представляет собой отдельный TS, чтобы добавить концепции в стандартную библиотеку, с помощью которой можно ограничить, например, алгоритмы.

Этот TS может быть немного далеко по дороге, но мы все еще можем взглянуть на то, как понятия были написаны до сих пор. Большинство примеров, которые я видел, несколько следуют за давней традицией, когда все вращается вокруг предполагаемого типа кандидата, который обычно не считается ссылочным типом. Например, некоторые из старых проектов Concepts-Lite (например, N3580) упоминают такие понятия, как Container, которые имеют свои корни в SGI STL и выжить даже сегодня в стандартной библиотеке в виде 23.2 Требования к контейнеру.

Контрольный знак предварительной пересылки указывает, что связанные типы описываются так:

Тип значения X::value_type Тип объекта, хранящегося в контейнере. Тип значения должен быть Assignable, но не должен быть DefaultConstructible.

Если мы переведем это наивно на Concepts-Lite, это может выглядеть так:

template<typename X>
concept bool Container = requires(X x) {
   typename X::value_type;
   // other requirements...
};

В этом случае, если мы пишем

template<typename C>
    requires Container<C>
void example(C&& c);

то мы имеем следующее поведение:

std::vector<int> v;

// fine
// this checks Container<std::vector<int>>, and finds
// std::vector<int>::value_type
example(std::move(v));

// not fine
// this checks Container<std::vector<int>&>, and
// references don't have member types
example(v);

Существует несколько способов выражения требования value_type, которое корректно обрабатывает эту ситуацию. Например. мы могли бы настроить требование typename std::remove_reference_t<X>::value_type вместо этого.

Я считаю, что члены Комитета знают об этой ситуации. Например. Эндрю Саттон оставляет проницательный комментарий в концептуальной библиотеке его, демонстрирующей точную ситуацию. Его предпочтительное решение состоит в том, чтобы оставить определение концепции для работы без ссылочных типов и удалить ссылку в ограничении. Для нашего примера:

template<typename C>
    // Sutton prefers std::common_type_t<C>,
    // effectively the same as std::decay_t<C>
    requires<Container<std::remove_reference_t<C>>>
void example(C&& c);

Ответ 2

T&& всегда имеет одно и то же "значение" - это ссылка rvalue на T.

Интересная вещь случается, когда T сама по себе является ссылкой. Если T=X&&, то T&&= X&&. Если T=X&, то T&&= X&. Правило о том, что ссылка rvalue на ссылку lvalue является ссылкой lvalue, - это то, что позволяет использовать ссылочную технику пересылки. Это называется ссылочным коллапсом 1.

Итак, для

template <typename T>
  requires Number<T>
void f(T&& test) {}

это зависит от того, что означает Number<T>. Если Number<T> позволяет передавать значения lvalue, то T&& будет работать как ссылка пересылки. Если нет, T&& он будет привязан только к значениям.

Поскольку остальные примеры (последний раз я проверял), определенный в терминах первого примера, есть у вас.

В спецификации понятий может быть дополнительная "магия", но я не знаю об этом.


1 На самом деле никогда не существует ссылки на ссылку. На самом деле, если вы наберете int y = 3; int& && x = y;, что является незаконным выражением: но using U = int&; U&& x = y; совершенно легально, когда происходит свертывание ссылок.

Иногда помогает аналогия с тем, как const работает. Если T const x; const, независимо от того, T - const. Если T_const - const, то T_const x; также const. И T_const const x; тоже const. const n x является максимальным значением const типа типа T и любых "локальных" модификаторов.

Аналогично, lvalue-ness ссылки является максимальным значением l T и любыми "локальными" модификаторами. Представьте, что язык имел два ключевых слова, ref и lvalue. Замените & на lvalue ref и && на ref. Использование lvalue без ref является незаконным при этом переводе.

T&& означает T ref. Если T было int lvalue ref, тогда свертывание ссылок приводит к int lvalue ref refint lvalue ref, которое переводится как int&. Аналогично, T& переводит на int lvalue ref lvalue refint lvalue ref, а если T= int&&, то T& переводится на int ref lvalue refint lvalue refint&.

Ответ 3

Это сложная вещь. В основном, когда мы пишем понятия, мы хотим сосредоточиться на определении типа (что мы можем сделать с T), а не на его различных формах (const T, T&, T const& и т.д.). Обычно вы спрашиваете: "Могу ли я объявить такую ​​переменную? Могу ли я добавить эти вещи?". Эти вопросы, как правило, действительны независимо от ссылок или cv-квалификаций. За исключением случаев, когда это не так.

При пересылке вычитание аргументов шаблона часто дает вам те, которые над формами (ссылки и cv-квалифицированные типы), поэтому вы в конечном итоге задаете вопросы о неправильных типах. вздох. Что делать?

Вы либо пытаетесь определить понятия для размещения этих форм, либо пытаетесь перейти к основному типу.