Инициализация вложенного списка не соответствует конструктору, принимающему initializer_list в качестве второго аргумента
Я разрабатываю структуру данных, способную описать семантику некоторых файлов XML в C++. Идея заключается в проверке наличия и / или правильной последовательности различных элементов при сохранении текста, который они содержат, в объект QHash с идентификаторами QString (на основе имен элементов, но настраиваемых) в качестве ключей.
Поскольку XML поддерживает вложение, я бы хотел иметь возможность отражать это вложение. Таким образом, каждый элемент XML описывается либо "name" и (необязательно) "id", что означает, что это последний лист и его текст будет проанализирован, либо "name" и списком других дескрипторов элемента, что означает, что должны быть эти вложенные элементы внутри текущего.
Поскольку таких семантических схем будет много, я бы хотел, чтобы код, описывающий их отдельные экземпляры, был действительно компактным.
Моя идея состояла в том, чтобы иметь класс, который описывает один элемент и может быть создан с помощью литералов C++ std::initializer_list, что я надеялся неявно поддерживать вложения. Различные перегруженные конструкторы, которые могут позже установить различные конкретные детали.
Я добрался почти до цели, но теперь застрял. Хотя конструктор с подписью constructor(std::initializer_list<ProtocolDescriptorTestNode >);
обычно вызывается для всех вложенных фигурных скобок, конструктор сигиллярной подписи constructor(QString, std::initializer_list<ProtocolDescriptorTestNode >);
никогда не вызывается, даже если я размещаю конструкции как{ "xyz", { {"abc", "123"}, {"def","456"} } }
в качестве инициала литерала.
Пожалуйста, посмотрите на следующие фрагменты кода, отдельно от кода тестирования, и помогите мне понять, если:
1. Это нормальное поведение C++11, и std:: initializer_list не поддерживает такое вложение в сочетании с другими параметрами типа данных.
2. Это вопрос реализации (bug;)) в gcc (я использую версию 4.9.1 (Debian 4.9.1-1))
3. Я упускаю некоторые действительно глупые детали в синтаксисе / семантике кода.
Объявление класса (выдержка из соответствующих конструкторов):
class ProtocolDescriptorTestNode {
public:
ProtocolDescriptorTestNode(const ProtocolDescriptorTestNode&);
ProtocolDescriptorTestNode(std::initializer_list<ProtocolDescriptorTestNode > init); // 1
ProtocolDescriptorTestNode(QString name, std::initializer_list<ProtocolDescriptorTestNode > init); // 2
ProtocolDescriptorTestNode(QString name, QString id, enum eElementMode = modeDefault); // 4
ProtocolDescriptorTestNode(QString name, enum eElementMode = modeDefault); //5
~ProtocolDescriptorTestNode() {}
QString name, id;
tProtocolDescriptorTestList list;
};
Определения соответствующих конструкторов:
ProtocolDescriptorTestNode::ProtocolDescriptorTestNode(std::initializer_list<ProtocolDescriptorTestNode> init)
{
qDebug() << "*** CONSTRUCTOR CALLED - 1 *** ";
qDebug() << init.size();
for(ProtocolDescriptorTestNode x : init) {
qDebug() << x.name << x.id;
}
}
ProtocolDescriptorTestNode::ProtocolDescriptorTestNode(QString name, std::initializer_list<ProtocolDescriptorTestNode> init) {
qDebug() << "*** CONSTRUCTOR CALLED - 2 *** ";
qDebug() << init.size();
for(ProtocolDescriptorTestNode x : init) {
qDebug() << x.name << x.id;
}
}
ProtocolDescriptorTestNode::ProtocolDescriptorTestNode(QString name, QString id, enum eElementMode) :
name(name),
id(id)
{
qDebug() << "*** CONSTRUCTOR CALLED - 4 *** ";
qDebug() << name << id;
}
ProtocolDescriptorTestNode::ProtocolDescriptorTestNode(QString name, enum eElementMode) :
name(name),
id("***")
{
qDebug() << "*** CONSTRUCTOR CALLED - 5 *** ";
qDebug() << name << id;
}
Экземпляр объекта тестирования: (примечание: неявное / явное преобразование типа данных char * / QString не имеет значения)
ProtocolDescriptorTestNode groupOther
({
{QString("name1"),"groupOther1"},
{"name2","groupOther2"},
{ QString("B"), {
{"name3","groupOther3"},
{
{"intra1","a"},
{QString("intra2")}
},
{"name4","groupOther4"}
} }
});
И соответствующая часть отладочного вывода, показывающая, что часть инициализации рядом с "B"
литерал трактуется как node(QString("B")
а также node(std::initializer_list)
сцепленный, а не node(QString("B"), std::initializer_list)
как было мое намерение:
*** CONSTRUCTOR CALLED - 4 ***
"name1" "groupOther1"
*** CONSTRUCTOR CALLED - 4 ***
"name2" "groupOther2"
*** CONSTRUCTOR CALLED - 5 ***
"B" "***"
*** CONSTRUCTOR CALLED - 4 ***
"name3" "groupOther3"
*** CONSTRUCTOR CALLED - 4 ***
"intra1" "a"
*** CONSTRUCTOR CALLED - 5 ***
"intra2" "***"
*** CONSTRUCTOR CALLED - 1 ***
1
"intra2" "***"
*** CONSTRUCTOR CALLED - 1 ***
2
"intra1" "a"
"" ""
*** CONSTRUCTOR CALLED - 4 ***
"name4" "groupOther4"
*** CONSTRUCTOR CALLED - 1 ***
3
"name3" "groupOther3"
"" ""
"name4" "groupOther4"
*** CONSTRUCTOR CALLED - 1 ***
2
"B" "***"
"" ""
*** CONSTRUCTOR CALLED - 1 ***
3
"name1" "groupOther1"
"name2" "groupOther2"
"" ""
1 ответ
Проблема, с которой вы сталкиваетесь, заключается в том, что если у типа есть конструктор с одним параметром типа initializer_list
(или имеет дополнительные параметры, но остальные имеют аргументы по умолчанию), инициализация списка всегда будет отдавать предпочтение этому конструктору по сравнению с другими конструкторами (§13.3.1.7 / 1 [over.match.list]). Имея это в виду, давайте пройдемся по вашей инициализации.
На внешнем уровне у вас есть braced-init-list, который содержит 3 элемента:
{
{QString("name1"),"groupOther1"} // element 1
{"name2","groupOther2"} // element 2
{ QString("B"), ... } // element 3
}
ProtocolDescriptorTestNode
имеет конструктор, который принимает один аргумент типа initializer_list<ProtocolDescriptorTestNode>
, Из-за правила, которое я упоминал ранее, компилятор попытается преобразовать каждый из этих трех элементов в ProtocolDescriptorTestNode
,
Каждый из этих элементов сам по себе является скобками-init-списками, поэтому будет сделана попытка сопоставить initializer_list
конструктор первый.
Рассмотрим первый элемент:
{QString("name1"),"groupOther1"}
Чтобы преобразовать это в initializer_list<ProtocolDescriptorTestNode>
, второй аргумент потребует 2 пользовательских преобразования, сначала QString
а затем ProtocolDescriptorTestNode
, что не допускается. Так что другие конструкторы ProtocolDescriptorTestNode
рассматриваются, и это соответствует конструктору 4.
Обратите внимание, что поведение было бы другим, если бы первый элемент был
{QString("name1"),QString("groupOther1")}
В этом случае каждый элемент списка braced-init-list будет соответствовать конструктору 5 для создания
ProtocolDescriptorTestNode
экземпляр, и тогда они будут образовыватьinitializer_list<ProtocolDescriptorTestNode>
и соответствовать конструктору 1.
Следующий элемент
{"name2","groupOther2"}
который снова соответствует конструктору 4 по той же причине, что и в последнем случае.
Третий элемент
{ QString("B"),
{
{"name3","groupOther3"}, // constructor 4
{
{"intra1","a"}, // constructor 4
{QString("intra2")} // constructor 1
}, // constructor 1
{"name4","groupOther4"} // constructor 4
} // constructor 1
} // constructor 1
Первый подэлемент неявно преобразуется в ProtocolDescriptorTestNode
(соответствующий конструктор 5), и второй подэлемент, сам по себе являющийся списком фигурных скобок, также может быть преобразован в ProtocolDescriptorTestNode
(соответствующий конструктор 1), поскольку каждый из подэлементов в этом фигурном списке инициализации самостоятельно неявно преобразуется в ProtocolDescriptorTestNode
сопоставляя различные конструкторы, как указано в комментариях выше.
Таким образом, конструктор 2 никогда не совпадает.
После этого многословного объяснения решение вашей проблемы на удивление просто и намекается на объяснение первого элемента. Элемент причины 3 соответствует initializer_list
Конструктор потому, что оба его подэлемента неявно преобразуются в ProtocolDescriptorTestNode
, Итак, замени QString("B")
с "B"
, Теперь его преобразование в ProtocolDescriptorTestNode
требует двух пользовательских преобразований, и initializer_list
Конструктор больше не жизнеспособен. Будут рассмотрены другие конструкторы, и конструктор 2 будет сопоставлен.