Инициализация вложенного списка не соответствует конструктору, принимающему 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 будет сопоставлен.

Живая демо

Другие вопросы по тегам