Существуют ли какие-либо планы в стандарте С++ для устранения несогласованности конструкторов списка инициализаторов?

конструкторы списка инициализаторов в С++ часто вызывают проблемы; например

using std::vector;
using std::string;
vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3

(Просто для уточнения, я имею в виду конструктор <int> - это конструктор списка инициализаторов, а <string> - нет.)

Случай int соответствует конструктору списка инициализаторов, а случай string - нет. Это несколько уродливо и часто вызывает проблемы. Это также было отмечено в ранней главе (пункт 7) в "Эффективном современном С++" Скотта Мейерса, и он описывает ее как несколько неприятную часть стандарта, когда всякий раз, когда имеется конструктор списка инициализаторов, компиляторы будут проходить через обручи, чтобы попытаться сопоставьте его, придав ему приоритет над каждым другим конструктором.

Конечно, случай int можно легко зафиксировать, изменив u{3} на u(3), но это не точка.

Является ли это желательным поведением? Были ли какие-либо обсуждения или планы со стороны стандартного комитета С++ для устранения этого типа двусмысленности/неприятности? Один пример требует, чтобы конструкторы списка инициализаторов вызывались следующим образом: vector<int> u({3}), который уже в настоящее время является законным.

Ответ 1

Были ли какие-либо обсуждения или планы со стороны стандартного комитета С++ для устранения этого типа двусмысленности/неприятности?

Было много исправлений для инициализации, поскольку С++ 11. Например, вы изначально не могли копировать агрегаты конструкции, используя list-initialization (CWG 1467). Это действительно незначительное исправление нарушило некоторый код нежелательным образом, что привело к новой проблеме, чтобы уточнить предыдущее исправление, чтобы отменить его, если существует конструктор initializer_list (CWG 2137). Трудно прикоснуться к чему-либо в этих статьях без множества неожиданных последствий и нарушения кода даже в небольших случаях. Я сомневаюсь, что в будущем будет сделано какое-то большое изменение для инициализации. В этот момент количество разломов кода будет огромным.

Лучшее решение - это просто знать о ловушках инициализации и быть осторожным с тем, что вы делаете. Мое правило состоит в том, чтобы использовать только {}, когда мне преднамеренно нужно поведение, которое {} предоставляет, и () в противном случае.

Обратите внимание, что это ничем не отличается от более известной ловушки:

vector<int> a{10}; // vector of 1 element
vector<int> b(10); // vector of 10 elements

В одном примере требуется, чтобы конструкторы списка инициализаторов вызывались следующим образом: vector<int> u({3}), который уже в настоящее время является законным.

У вас такая же проблема, как и раньше, по тем же причинам:

vector<int> u({3});    // vector of one element: 3
vector<string> v({3}); // vector of three elements: "", "", and ""

Даже если вы должны были потребовать первое (что было бы невозможно), вы не могли бы сделать последнее плохо сформированным.

Ответ 2

Сначала это унифицированный инициализатор, и он был введен для решения двусмысленности языка. Эта проблема называлась Most Vexing Parse в отношении объявления переменных, инициализированных скобкой "round" (). MVP - это разновидность разрешения двусмысленности в коде, аналогичном следующему:

class  SomeInitClass;


void  bleh()
{
       int foo(SomeInitClass());
}

foo здесь фактически является прототипом функции, которая принимает в качестве параметра функцию, возвращающую строку, а возвращаемое значение функции foo - это int. По сути, если что-то похоже на прототип, С++ рассматривает его как таковой.

int foo{SomeInitClass{}};

SomeInitClass{} всегда создает временное. int foo{...} всегда создавал бы переменную.

Хотя эти две строки работают по-разному:

vector<string> v{3}; // vector of three empty strings
vector<int> u{3}; // vector of one element with value 3

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

Это не несогласованность, а не серьезная. vector<string> и vector<int> НЕ являются одним и тем же классом и не имеют одинаковых конструкторов, потому что они не являются идентичными экземплярами шаблона std::vector. Чтобы избежать путаницы, вы можете использовать псевдонимы и использовать несколько иной синтаксис.

 StringCollecton v{3}; //three strings;
 IntCollection   u = {3}; // or {{3}}

Конечно, StringCollecton test = {3}; не будет работать в этом случае, потому что 3 не является литералом, который может быть введен в надлежащий сохраненный тип.

Поскольку в объявлении может быть только один инициализатор, установка значений созданного контейнера строк будет выглядеть так:

std::vector<std::string> test{3, {"string"}}; // all values initialized by "string"

std::vector<std::string> test{{"string1","",""}}; // constructor introduced in C++11

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