内部 class 使用外部 class 模板参数但仅在使用 `ostream` 时编译失败

Compilation failure when inner class uses outer class template parameter but only when using `ostream`

当内部 class 使用外部 class 的模板参数时,我 运行 进入编译器错误,并且我在外部 class 上实例化输出流运算符] 在内部类型的成员上。

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

代码如下:

#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 << 上收到一个编译器错误,它为内部调用了相应的输出流运算符。我不会发布编译器错误消息的全部内容;正是我认为相关的内容。

$ 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;
         ~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~

(截取了其余的编译器错误)

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

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

您遇到编译错误,因为 non-deduced context 导致模板参数推导失败。

In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

  1. The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id:

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

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

你可以让 operator<< 成为非模板(绕过类型推导的麻烦),然后你必须在 class 定义中将它定义为 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::cout << g.innerChildren_[i] << std::endl; // Line 51

成为

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

因为您试图在未定义的地方使用运算符 <<

如果依赖第(27)行中定义的运算符,修改第(50)行以正确调用它,如下

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