【问题标题】:Compilation failure when inner class uses outer class template parameter but only when using `ostream`内部类使用外部类模板参数但仅在使用`ostream`时编译失败
【发布时间】:2020-07-04 15:35:51
【问题描述】:

当内部类使用外部类的模板参数时遇到编译器错误,我在内部类型的成员上实例化外部类的输出流运算符。

我花了很多时间试图解决这个问题。我相信以下来源很接近,但我仍然不明白为什么我会遇到编译失败。

代码如下:

#include <iostream>
#include <vector>

template <typename T>
struct Outer
{
    struct Inner
    {
        Inner(const T& val = T());
        T data_;
    }; // end class Inner

    Outer();

    void AddInnerChildToOuter(const T& data);
    std::vector<typename Outer<T>::Inner> innerChildren_;
}; // end class Outer


// Inner constructor
template <typename T>
Outer<T>::Inner::Inner(const T& val) : data_(val)
{
}

template <typename T>
std::ostream& operator<<(std::ostream& strm, // Line 27
                         const typename Outer<T>::Inner& gn)
{
    strm << gn.data_ << std::endl;
    return strm;
}

// Outer constructor
template <typename T>
Outer<T>::Outer()
{
}

template <typename T>
void Outer<T>::AddInnerChildToOuter(const T& data)
{
    typename Outer<T>::Inner node(data);
    innerChildren_.push_back(node);
}

template <typename T>
std::ostream& operator<<(std::ostream& strm, const Outer<T>& g)
{
    for (size_t i = 0; i < g.innerChildren_.size(); ++i)
        std::cout << g.innerChildren_[i] << std::endl; // Line 51
    return strm;
}

int main()
{
    Outer<int> g;
    g.AddInnerChildToOuter(3);
    g.AddInnerChildToOuter(5);
    std::cout << g << std::endl; // Line 60
    return 0;
}

我在外部调用了相应的输出流运算符的外部ostream operator &lt;&lt; 出现编译器错误。我没有发布编译器错误消息的全部内容;就是我认为相关的。

$ g++ -Wall -W -Wextra -pedantic -ansi OuterInnerArgh.cpp 
OuterInnerArgh.cpp: In instantiation of ‘std::ostream& operator<<(std::ostream&, const Outer<T>&) [with T = int; std::ostream = std::basic_ostream<char>]’:
OuterInnerArgh.cpp:60:18:   required from here
OuterInnerArgh.cpp:51:19: error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘const Outer<int>::Inner’)
         std::cout << g.innerChildren_[i] << std::endl;
         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~

(截断编译器对各种ostream 重载的尝试;下面有更多编译器错误消息)

OuterInnerArgh.cpp:27:15: note: candidate: template<class T> std::ostream& operator<<(std::ostream&, const typename Outer<T>::Inner&)
 std::ostream& operator<<(std::ostream& strm,
               ^~~~~~~~
OuterInnerArgh.cpp:27:15: note:   template argument deduction/substitution failed:
OuterInnerArgh.cpp:51:19: note:   couldn't deduce template parameter ‘T’
         std::cout << g.innerChildren_[i] << std::endl;
         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
OuterInnerArgh.cpp:48:15: note: candidate: template<class T> std::ostream& operator<<(std::ostream&, const Outer<T>&)
 std::ostream& operator<<(std::ostream& strm, const Outer<T>& g)
               ^~~~~~~~
OuterInnerArgh.cpp:48:15: note:   template argument deduction/substitution failed:
OuterInnerArgh.cpp:51:19: note:   ‘const Outer<int>::Inner’ is not derived from ‘const Outer<T>’
         std::cout << g.innerChildren_[i] << std::endl;
         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~

(截断其余的编译器错误)

请告诉我为什么会遇到编译器错误 -

  • 即使我有一个ostream operator &lt;&lt; 代表typename Outer&lt;T&gt;::Inner&amp;
  • 即使我在相关的地方“洒”了typename“magic-dust”
  • 仅适用于外部ostream 运算符,不适用于构造函数或内部ostream 运算符(后者可能根本没有实例化?)

为什么编译器会说‘const Outer&lt;int&gt;::Inner’ is not derived from ‘const Outer&lt;T&gt;’? (是的,没有继承,但是内部类型定义嵌套在外部)

【问题讨论】:

  • 我已经承认(见我问题的最后一行)没有继承。
  • 是的,但是在另一个类中声明一个类不会使它派生自任何东西。
  • 我想我明白你在说什么,但似乎我需要澄清我在说什么:我要问的是,为什么编译器会假设 Outer&lt;int&gt;::Inner表示Inner 派生Outer&lt;int&gt;?为什么它不能是Outer&lt;int&gt; 中定义的类型——就像这里的情况一样?
  • 我明白了。它不假设 - Outer 的重载是可用的候选者之一,编译器会告诉你为什么不能使用它。模板错误消息并不有趣,即使它们比以前更好。

标签: c++ templates inner-classes


【解决方案1】:

由于non-deduced context 导致模板参数推导失败,因此出现编译错误。

在以下情况下,用于构成 P 的类型、模板和非类型值不参与模板参数推导,而是使用在其他地方推导或显式指定的模板参数。如果模板参数仅在非推导上下文中使用且未显式指定,则模板参数推导失败。

  1. 使用限定 ID 指定的类型的嵌套名称说明符(范围解析运算符 :: 左侧的所有内容):

例如,如果您明确指定模板参数(以丑陋的风格),它将编译。在operator&lt;&lt; 中为Outer

operator<< <T> (strm, g.innerChildren_[i]);
//         ^^^

你可以将operator&lt;&lt;设为非模板(绕过类型推断的麻烦),然后你必须在类定义中将它定义为friend。例如

struct Inner
{
    Inner(const T& val = T());
    T data_;
    friend std::ostream& operator<<(std::ostream& strm,
                                    const Inner& gn)
    {
        strm << gn.data_ << std::endl;
        return strm;
    }
};

LIVE

【讨论】:

  • 欣赏关于非推断上下文的链接。也非常感谢你用两种不同的方法来解决这个问题。你能详细说明一下混乱的地方吗? std::ostream 重载适用于我的代码(就像用goodbad 示例为identity 解释的链接)?
  • @HappyGreenKidNaps 抱歉,我无法准确回答您的问题。您的具体困惑是什么?
  • 为什么编译器看不到第 27 行定义的 您建议的代码更改如何帮助编译器解决参数推导问题?
  • @HappyGreenKidNaps 编译器确实看到了它,即它是通过名称查找找到的。但是模板参数T不能从函数参数中推导出来,调用失败。对于第一种解决方案,我们明确指定模板参数以绕过扣除。对于第二种解决方案,我们将operator&lt;&lt; 设为非模板运算符,则不需要模板参数推导。
  • 非常感谢@songyuanyao。
【解决方案2】:

解决这个问题

std::cout << g.innerChildren_[i] << std::endl; // Line 51

成为

std::cout << g.innerChildren_[i].data_ << std::endl; // Line 51

因为您尝试使用未定义的运算符&lt;&lt;

如果依赖第(27)行定义的操作符,修改第(50)行使其正确调用,如下

for (size_t i = 0; i < g.innerChildren_.size(); ++i)
{
    operator<< <T>(strm, g.innerChildren_[i]);
    strm << std::endl;
}

【讨论】:

  • 感谢您的回答。虽然它解决了问题,但没有解释问题。定义了一个 operator &lt;&lt;(第 27 行)。
  • 感谢您的更新/编辑。我仍然无法理解为什么编译器无法确定这一点(以及如何建议的代码更改然后告诉编译器选择什么重载定义)。
猜你喜欢
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多