【问题标题】:Initialising a struct that contains a vector of itself初始化一个包含自身向量的结构
【发布时间】:2026-02-20 09:45:02
【问题描述】:

我有一个菜单系统,我想从常量数据中初始化。 MenuItem 可以包含MenuItems 的向量作为子菜单。但它只能在一定程度上起作用。以下是问题的核心:

#include <vector>
struct S { std::vector<S> v ; } ;

S s1 = { } ;
S s2 = { { } } ;
S s3 = { { { } } } ;

g++ -std=c++0x(版本 4.4.5)处理 s1s2,但 s3 回来了:

prog.cpp:6:22: error: template argument 1 is invalid

(请参阅ideone)。我做错了吗?

【问题讨论】:

标签: c++ stl c++11 initializer-list incomplete-type


【解决方案1】:

boost::optional 和 boost::recursive_wrapper 看起来对此很有用

struct S { // one brace
    boost::optional< // another brace
      boost::recursive_wrapper< // another brace
        std::vector< // another brace
          S
        >
      >
    > v; 
};

您添加的每个子菜单都需要 4 个大括号。当涉及构造函数调用时,不会发生大括号省略。例如

S m{{{{ 
  {{{{ }}}}, 
  {{{{ 
    {{{{ }}}}, 
    {{{{ }}}} 
  }}}} 
}}}}; 

老实说,使用构造函数看起来更具可读性

struct S {
    // this one is not really required by C++0x, but some GCC versions
    // require it.
    S(char const *s)
    :v(s) { } 

    S(std::string const& s)
    :v(s) { }

    S(std::initialize_list<S> s)
    :v(std::vector<S>(s)) { } 

    boost::variant<
      std::string,
      boost::recursive_wrapper<
        std::vector<
          S
        >
      >
    > v; 
};

现在它简化为

S s{ 
  "S1", 
  {
    "SS1",
    "SS2",
    { "SSS1", "SSS2" }
  }
};

【讨论】:

    【解决方案2】:

    您正在尝试做的是一个即将推出当前 C++ 的功能,称为“初始化列表”,其中可以使用 = { } 初始化向量或列表。 我不知道他们是否在TR1中推出了它。也许它在 TR2 中。

    #include <vector>
    #include <list>
    #include <iostream>
    using namespace std;
    int main(void) {
        vector<int> vi = {1, 2, 3, 4, 5};
        list<int> li = {5, 4, 3, 2, 1, 0};
        cout<<"vector size="<<vi.size()<<", list size="<<li.size()<<endl;
        return 0;
    }
    

    您使用的代码对我来说看起来不合适。如果要实现包含结构(树)的结构,请在节点中包含指向结构/节点的指针列表(或者如果无法实现,则仅包含 void 指针)。

    大多数菜单结构本质上是一个有序的基于列表的树(一个地方有 n 个节点,但其他地方可能是 m 个节点,等等)。 Robert Sedgewick 编写了一本教科书“C++ 中的算法”。

    #include <vector>
    #include <iterator>
    #include <string>
    void * pRoot = NULL; //pointer to CTree
    class CTreenode;
    class CTree;
    class CTree {
        public:
            vector<class CTreeNode> lctnNodeList; //list does not have at() or operator[]
            vector<class CTreeNode>::iterator lctni;
        public:
            CTree() {}
            ~CTree() {
                for (lctni=lctnNodeList.begin(); lctni != lctnNodeList.end(); nctni++) {
                    if (NULL==lctni->getChildPtr()) {
                        //do nothing, we have already done all we can
                    } else {
                        delete (CTree *)lctnNodeList.pChild;
                    }
                    //do action here
                }
            }
            void addToList(string& data, CTree * p) { 
                CTreeNode ctn(data, p);
                lctnNodeList.push_back(d); 
            }
            void eraseAt(size_t index) { 
                vector<class CTreeNode>::iterator i = lctnNodeList.begin();
                vector<class CTreeNode>::iterator i2 = lctnNodeList.begin();
                i2++;
                size_t x;
                for (x=0; x <= index; x++,i++,i2++) {
                    if (index == x) {
                        lctnNodeList.erase(i,i2);
                        break;
                    }
                }
            }
            void at(size_t index, string& returndata, CTree * &p) { 
                vector<class CTreeNode>::iterator i = lctnNodeList.begin();
                size_t x;
                for (x=0; x <= index; i++,x++) {
                    if (x==index) {
                         i->getData(returndata, p);
                         break;
                    }
                }
            }
            const CTreeNode& operator[](const size_t idx) {
                if (idx < lctnNodeList(size)) {
                    return lctnNodeList.at(idx);
                } else {
                    //throw an error
                }
            }
            const size() {
                return lctnNodeList.size();
            }
            //this can be applied to the root of the tree (pRoot).
            doActionToThisSubtree(void * root) {
                CTree * pct = (CTree *)root;
                for (pct->lctni=pct->lctnNodeList.begin(); pct->lctni != pct->lctnNodeList.end(); pct->nctni++) {
                    //this is a depth-first traversal.
                    if (NULL==pct->lctni->getChildPtr()) {
                        //do nothing, we have already done all we can
                        //we do this if statement to prevent infinite recursion
                    } else {
                        //at this point, recursively entering child domain
                        doActionToThisSubtree(pct->lctni->getChildPtr());
                        //at thisd point, exiting child domain
                    }
                    //do Action on CTreeNode node pct->lctni-> here.
                }
            }
    };
    class CTreeNode {
        public:
            CTree * pChild; //CTree *, may have to replace with void *
            string sData;
        public:
            CTreeNode() : pChild(NULL) {}
            CTreeNode(string& data, pchild) : pChild(pchild) {
                sData = data;
            }
            ~CTreeNode() { 
                 if (NULL!=pChild) { 
                     delete pChild;//delete (CTree *)pChild; 
                     pChild = NULL; 
                 }
            void getChild(CTreeNode& child) { 
                child = *pChild;//child = *((CTree *)pChild); 
            }
            bool addChild(string& s) { 
                if (NULL==pChild) {
                    return false;
                } else {
                    pChild = new CTree;
                }
                return true;
            }
            void * getChildPtr() { return pChild; }
            void getData(string& data, CTree * &p) { //not sure about how to put the & in there on CTree
                data=sData;
                p = pChild;
            }
            void setData(string& data, CTree * p) {
                sData=data;
                pChild = p;
            }
    };
    

    这里的问题是相互依赖,我想我已经用类声明解决了。 做类 CTreeNode;在 CTree {} 类之前。 http://www.codeguru.com/forum/showthread.php?t=383253

    我可能正在修改这段代码,而且它不完整,因为我已经好几年没有需要写一棵树了,但我想我已经涵盖了基础知识。我没有实现 operator[]。

    【讨论】:

    • std::initialize_list&lt;&gt; 与库功能一样是语言功能,因此不能成为技术报告的一部分(即,仅是 C++0x 的一部分)。
    【解决方案3】:

    GMan 在他的评论中是正确的:在您的代码中 S::v 的声明中,S 仍然不完整。类型必须完整才能用作 STL 容器中的值类型。更多信息请参阅 Matt Austern 的文章"The Standard Librarian: Containers of Incomplete Types."

    如果您要切换到可用于不完整类型的容器,那么您的代码就可以了。例如,给定以下内容:

    #include <initializer_list>
    
    template <typename T>
    struct Container
    {
        Container() { }
        Container(std::initializer_list<T>) { }
    };
    
    struct S { Container<S> v; };
    

    那么您的原始初始化应该可以正常工作:

    S s3 = { { { } } } ;
    

    这也可以:

    S s4 = { { { { { { { { { { { { { { { { /*zomg*/ } } } } } } } } } } } } } } } };
    

    【讨论】:

    • 这不是过于简单化到荒谬的地步了吗?那不是“包含自身向量的结构”,而且您还没有“切换到允许不完整类型的容器”……因为它们都不包含任何数据!此代码不存储任何内容,也不执行任何操作。在我看来,这与“一个包含自身的对象”的价值不可能性非常相似。