2

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

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

由于会有很多这样的语义方案,我希望描述它们的各个实例的代码非常紧凑。

我的想法是有一个类,它描述一个元素并且可以通过 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. 这是 gcc 中的实现问题(bug ;))(我使用的是 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"
"" ""
4

1 回答 1

0

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

在最外层,您有一个包含 3 个元素的支撑初始化列表:

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

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

这些元素中的每一个本身都是支撑初始化列表,因此将首先尝试匹配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 ),因为这个花括号内的每个子元素-init-list 本身可以ProtocolDescriptorTestNode通过匹配各种构造函数来隐式转换为,如上面的注释所示。

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


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

现场演示

于 2014-08-01T05:03:22.133 回答