#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 Так же, как вы делаете сегодня — используйте
{}
s3. Я чувствую, что хотел бы иметь что-то вроде
-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? Если ответ положительный, то ()
инициализация агрегата не может предотвратить сужение.