嵌套列表初始化与将initializer_list作为第二个参数的构造函数不匹配

时间:2014-08-01 00:42:29

标签: c++ c++11 g++ initializer-list list-initialization

我正在开发能够用C ++描述某些XML文件语义的数据结构。想法是检查各种元素的存在和/或正确顺序,同时将它们包含的文本存储到QHash对象中,并使用QString id(基于元素名称,但可自定义)作为键。

由于XML支持嵌套,我希望能够镜像这种嵌套。因此,每个XML元素都由“name”和(可选)“id”描述,这意味着它是最终的叶子,它的文本将被解析,或“名称”和其他元素描述符的列表,这意味着应该有这些当前的嵌套元素。

由于会有很多这样的语义方案,我想让描述其个别实例的代码非常紧凑。

我的想法是有一个类,它描述了一个元素,可以通过c ++ std :: initializer_list文字构建,我希望隐式支持嵌套。各种重载的构造函数可以在以后设置各种特定的细节。

我几乎到了那里,但现在卡住了。即使具有签名constructor(std::initializer_list<ProtocolDescriptorTestNode >);的构造函数被正常地为每个嵌套的花括号调用,但是simillar签名构造函数constructor(QString, std::initializer_list<ProtocolDescriptorTestNode >);从未被调用,即使我放置了像{{ {1}}
作为初始化文字。

请查看以下代码片段,与测试代码分开,并帮助我理解是否:
1。这是正常的c ++ 11行为,std :: initializer_list不支持这种嵌套与其他数据类型参数的组合。
2.这是gcc中实现的问题(bug;))(我使用的是版本4.9.1(Debian 4.9.1-1))
我在代码语法/语义中忽略了一些非常愚蠢的细节。

类声明(相关构造函数的摘录):

{ "xyz", { {"abc", "123"}, {"def","456"} } }

相关构造函数的定义:

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;
};

测试对象实例: (注意:隐式/显式数据类型转换char * / QString没有区别)

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;
}

调试输出的相关部分,显示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)

1 个答案:

答案 0 :(得分:0)

您遇到的问题是,如果某个类型的构造函数具有类型为initializer_list的单个参数(或具有其他参数,但其余参数具有默认参数),则列表初始化将始终为比其他构造函数更喜欢构造函数§13.3.1.7/ 1 [over.match.list] )。考虑到这一点,让我们按照初始化的方式工作。

在最外层,你有一个包含3个元素的braced-init-list:

{
    {QString("name1"),"groupOther1"}  // element 1
    {"name2","groupOther2"}           // element 2
    { QString("B"), ... }             // element 3
}

ProtocolDescriptorTestNode有一个构造函数,它接受initializer_list<ProtocolDescriptorTestNode>类型的单个参数。由于我之前提到的规则,编译器将尝试将这3个元素中的每一个转换为ProtocolDescriptorTestNode

这些元素中的每一个本身都是braced-init-lists,因此首先尝试匹配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 ),第二个子元素本身也是一个braced-init-list,也可以转换为{ {1}}(匹配构造函数1 ),因为这个braced-init-list中的每个子元素本身都可以通过匹配各种构造函数隐式转换为ProtocolDescriptorTestNode,如上面的评论。

因此,构造函数2 永远不会匹配。


经过长时间的解释之后,解决问题的方法非常简单,并在第一个元素的解释中暗示。 元素3 ProtocolDescriptorTestNode构造函数匹配的原因是因为它的两个子元素都可以隐式转换为initializer_list。因此,请将ProtocolDescriptorTestNode替换为QString("B")。现在,它转换为"B"需要两次用户定义的转换,ProtocolDescriptorTestNode构造函数不再可行。将考虑其他构造函数,构造函数2 将匹配。

Live demo