【问题标题】:C++ templates: Returning list::iteratorC++ 模板:返回 list::iterator
【发布时间】:2011-04-11 07:24:32
【问题描述】:

我怎样才能使下面的代码工作?在编译期间,我收到一条错误消息,告诉我 searchForResource 函数没有返回类型。

template<class T>
class ResourceManager
{
  private:
    struct ResourceWrapper;
    std::list<ResourceWrapper*> resources_; // This compiles fine

    std::list<ResourceWrapper*>::iterator  // Error occurs here
        searchForResource(const std::string& file);
};

另外,我会这样定义searchForResource 函数吗?

template<class t>
std::list<typename ResourceManager<T>::ResourceWrapper*>::iterator
    ResourceManager<T>::searchForResource(const std::string& file)
{
    // ...
}

【问题讨论】:

    标签: c++ templates


    【解决方案1】:

    std::list&lt;ResourceWrapper*&gt;::iterator 编译器很难理解。在实现和声明中都使用 typename 作为前缀,让编译器知道它是一个类型。

    像这样:

    typename std::list<ResourceWrapper*>::iterator searchForResource(const std::string& file);
    

    【讨论】:

    • 实施即定义。将此应用于声明和定义。
    • “硬”是什么意思?它要么可以,要么不能。另外,std::list::iterator 中的“硬”是什么?您的解决方案可能是正确的,但解释无济于事
    • 这取决于编译器何时解析文本。如果编译器不解析任何模板文本,将其视为一个美化的宏,那么知道std::list&lt;ResourseWrapper*&gt;::iterator 是一个类型的名称就没有问题。如果编译器在读取模板时解析文本,而不仅仅是在实例化它时,它必须知道哪些符号代表类型名和模板;否则,您将无法解析 C++。如果名称不依赖,则可以在范围内查找;在这种情况下,名称是依赖的,所以你必须告诉编译器它是一个类型名。
    • James 讲得很好。我发现这个解释也很有帮助:womble.decadent.org.uk/c++/template-faq.html#disambiguation
    • @Slavik81:编译器无法预先弄清楚。 typename 是标准要求的,因为ResourceWrapper 是一个依赖标识符。请注意,全名是ResourceManager&lt;T&gt;::ResourceWrapper,名称中的T 意味着对于特定T 可能存在std::list 的特化,这可能会将std::list&lt;ResourceWrapper&gt; 的含义更改为包含非类型成员iterator
    【解决方案2】:
    template<class T>
    class ResourceManager
    {
      private:
        struct ResourceWrapper;
        std::list<ResourceWrapper*> resources_;
    
    //      | typename lost here
    //      V
        typename std::list<ResourceWrapper*>::iterator
            searchForResource(const std::string& file);
    };
    
    template<class T>
    //  | typename lost here                    asterisk lost here | 
    //  V                                                          V 
    typename std::list<typename ResourceManager<T>::ResourceWrapper*>::iterator
        ResourceManager<T>::searchForResource(const std::string& file)
    {
       return ...   
    }
    

    【讨论】:

      【解决方案3】:

      我认为您缺少 typename 关键字。

      【讨论】:

      • 执行std::list&lt;typename ResourceWrapper*&gt;::iterator ... 会出现同样的错误。
      • 你把类型名放错了地方。见斯拉维克的回答。
      【解决方案4】:

      有一个经验法则可以避免此类编译错误。

      无论何时声明一个变量或函数,使用模板后跟范围解析运算符::,然后始终将关键字typename放在定义前面。

      例如,

      MyNameSpace::MyClass<T> x; // Ok; because template is NOT followed by scope resolution
      MyNameSpace::MyClass<T>::MyType x; // Error; MyType can be a variable or a type; so put typename ahead
      

      同样的事情也适用于函数声明。

      【讨论】:

        【解决方案5】:

        问题在于ResourceWrapper 是一个从属名称*(它的定义取决于类型参数T),这使得std::list&lt; ResourceWrapper * &gt; 成为一个从属类型名称。模板在两遍中检查,在第一遍中,模板的正确性没有实际类型替换被检查。现在,当您键入std::list&lt; ResourceWrapper* &gt;::iterator 时,编译器无法预先知道iterator 实际上是一个类型,而不是std::list&lt; ResourceWrapper* &gt; 类的静态属性或成员,因为类型是相关的并且T 尚未被替换。

        您必须通过使用 typename 关键字来提示编译器以告知它 iterator 确实是一种类型,正如其他人之前已经提到的那样:

        typename std::list< ResourceWrapper* >::iterator
        

        没有看到其余代码,我不能说,但似乎ResourceWrapper 实际上不应该是T 的依赖类型。如果它实际上是非依赖的,则应该将类型移到类模板之外。在这种情况下,将不再需要 typename

        struct ResourceWrapper;
        template <typename T>
        class ResourceManager {
           std::list<ResourceWrapper*>::iterator searchForResource(const std::string& file);
        ...
        

        因为它是在模板之外定义的,所以 ResourceManager 模板的所有可能实例都有一个单一定义,现在 ResourceWrapper 不再依赖于 T,并且不再需要 typename(也正确)。

        * 为什么ResourceWrapper 依赖以及这对代码有何影响。

        ResourceWrapper 依赖于 T 类型的原因通过讨论完全限定名称更容易看出:::ResourceManager&lt;T&gt;::ResourceWrapperT 是类型的一部分,因此T 会影响ResourceWrapper 的实际定义。这在某种程度上是一个人为的例子,你可以说如果编译器正在解析这个特定的模板,那么它必须知道 ResourceWrapper 是一个类型,因此 std::list&lt; ResourceWrapper*&gt;::iterator 是一个类型......这是问题。没有特别的理由不为 ResourceManager 的特定实例化专门化 std::list 模板:

        namespace std { // you should in general not add things to the std namespace!
                        // but the implementation can
        template <>
        struct list< ResourceManager<int>::ResourceWrapper > {
           static const int iterator = 5;
        ...
        };
        }
        

        再一次,做作,但是编译器在解析模板时不可能预先知道在您实际实例化具有特定类型的模板之前不会出现这种特化。

        【讨论】:

          【解决方案6】:

          你有 ResourceWrapper 结构的前向声明,这对于编译良好的行来说已经足够了,但是你得到了错误,因为此时编译器需要 ResourceWrapper 结构的完整类型声明。 (可能是你的答案,这段代码实际上用VS2008编译得很好)

          【讨论】:

            猜你喜欢
            • 2010-12-06
            • 2014-01-22
            • 2016-06-12
            • 2013-03-29
            • 2022-12-14
            • 2014-05-28
            • 2021-10-21
            • 1970-01-01
            • 2010-09-12
            相关资源
            最近更新 更多