P0960, есть ли какой-нибудь механизм для обнаружения сужения в новых агрегатах init с () s в c ++ 20? - PullRequest
4 голосов
/ 08 апреля 2019

С помощью P0960 «Разрешить инициализацию агрегатов из заключенного в скобки списка значений», вы также можете выполнять агрегаты init с () 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 с круглыми скобками?

Ответы [ 2 ]

7 голосов
/ 08 апреля 2019

Инициализирующие агрегаты агрегаты допускают сужающие преобразования.

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

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

В частности:

 auto q = new Foo(INT_MAX, UINT_MAX); 

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

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

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

4 голосов
/ 10 апреля 2019

Вы не должны ограничивать узость при использовании инициализации ().

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

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

struct Agg { int i; };

Agg a{5.0f};

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

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

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

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

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

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

...