P0960, есть ли какой-либо механизм для определения, есть ли сужение в новых агрегатах, инициализируемых с помощью () s в c 20?

#c #c -standard-library #c 20 #narrowing

#c #c -standard-library #c 20 #сужение

Вопрос:

С помощью P0960 «Разрешить инициализацию агрегатов из списка значений, заключенного в скобки», вы также можете выполнять инициализацию агрегатов с () помощью s .

Однако эта инициализация допускает сужение, а {} s — нет.

 #include <vector>
#include <climits>

struct Foo
{
  int x, y;
};

int main()
{
  // auto p = new Foo{INT_MAX, UINT_MAX}; // still won't compile
  auto q = new Foo(INT_MAX, UINT_MAX);    // c  20 allows narrowing aggregates init

  std::vector<Foo> v;
  // v.emplace_back(Foo{INT_MAX, UINT_MAX}); // still won't compile
  v.emplace_back(INT_MAX, UINT_MAX);         // c  20 allows narrowing aggregates init
                                             // in furtherly perfect forwardings
}
  

Можно ли обнаружить преобразование сужения с помощью инициализации агрегата C 20 с помощью круглых скобок?

Комментарии:

1. Вы ищете конкретный ответ для компилятора?

2. @P.W «Что угодно» Я боюсь, что этот стандартный ход может привести ко многим тонким ошибкам из-за неосознанного сужения. Что еще более важно, идеальная пересылка теперь буквально «везде». Если кто-то может «ограничить» узость там, где он или она чувствует себя некомфортно, это должно быть таким облегчением.

Ответ №1:

Агрегаты, инициализирующие Paren, позволяют сужать преобразования.

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

Единственное отличие заключается в том, что агрегаты, инициализирующие paren, вычисляют выражения слева направо (тогда как при вызове конструктора мы имеем неопределенную оценку аргументов).


В частности:

  auto q = new Foo(INT_MAX, UINT_MAX); 
  

будет вести себя в основном так, как если бы вы действительно написали этот конструктор:

 struct Foo
{
  Foo(int x, int y) : x(x), y(y) { } // ~ish
  int x, y;
};
  

Который сам по себе не предупреждает ни о каком компиляторе, который я пробовал сегодня.

Комментарии:

1. Этот пример убивает! Но как нам тогда ограничить узость инициализации «Foo» «снаружи»?

2. @sandthorn Так же, как вы делаете сегодня — используйте {} s

3. Я чувствую, что хотел бы иметь что-то вроде -frestrict-narrowing-paren-aggregates-init по всем направлениям. Так что я не пропускаю, где я { } сам скучаю.

Ответ №2:

Предполагается, что вы не должны хотеть «ограничивать узость» при использовании инициализации () .

Основной смысл этой функции заключается в том, чтобы разрешить использование агрегатов в сценариях пересылки, таких как container::emplace конструкторы на месте и тому подобное. В настоящее время они не работают, потому std::allocator_traits<>::construct что синтаксис инициализации списка не будет и не может использовать синтаксис инициализации списка, поскольку слишком много случаев, когда он будет скрывать конструкторы, которые вы, возможно, захотите вызвать.

В сценариях пересылки способность точно отбирать сужающие преобразования ограничена при работе с агрегатами. Почему? Рассмотрим это:

 struct Agg { int i; };

Agg a{5.0f};
  

Это не сужающее преобразование, потому что это конкретное литеральное значение с плавающей запятой может быть преобразовано в an int без потери точности. Однако, когда вы пересылаете конструкцию через emplace и т.п., Компилятор не может видеть фактическое значение параметра. Он видит только типы. Итак, если бы вы должны были сделать:

 vector<Agg> v;
v.emplace_back(5.0f);
  

Все, что видит компилятор, это то, что emplace_back он попытается передать a float для агрегированной инициализации Agg . Это всегда сужающее преобразование и, следовательно, всегда незаконно.

Предотвращение сужения инициализации списка имеет некоторый смысл, поскольку списки инициализации с привязкой лучше всего использовать локально. Инициализируемый тип известен, и любые буквальные значения будут предоставлены непосредственно там, где {} используется. Таким образом, имеется достаточная информация для решения проблем сужения разумным образом.

Как только вы переходите к пересылке, это просто не работает. Предотвращение сужения приведет к отбору параметров, значения которых локально были бы в порядке.

Итак, вопрос в следующем: вы хотите emplace(X, Y, Z) работать так же хорошо, как Agg{X, Y, Z} и раньше, для всех допустимых X, Y и Z? Если ответ положительный, то () инициализация агрегата не может предотвратить сужение.