【问题标题】:operator<<(ostream&, X) for class X nested in a class template嵌套在类模板中的类 X 的 operator<<(ostream&, X)
【发布时间】:2011-07-04 07:44:50
【问题描述】:

这个编译和工作正常(非嵌套模板):

#include <iostream>

template<typename T> class Z;

template <typename T> 
std::ostream& operator<< (std::ostream& os, const Z<T>&) {
    return (os << "Z");
}

template<typename T> class Z {
    friend std::ostream& operator<< <> (std::ostream& os, const Z&);
};

int main () {
    Z<int> z;
    std::cout << z << std::endl;
}

这个不能编译(gcc 4.4 和 gcc 4.6,在 03 和 0x 模式下):

#include <iostream>

template<typename T> class Z;

template<typename T> 
std::ostream& operator<< (std::ostream& os, const typename Z<T>::ZZ&) {
    return (os << "ZZ!");
}

template <typename T> class Z {
  public:
    class ZZ {
        friend std::ostream& operator<< <> (std::ostream& os, const ZZ&);
    };
};


int main () {
    Z<int>::ZZ zz;
    std::cout << zz << std::endl;
}

错误信息如下所示:

error: template-id ‘operator<< <>’ for ‘std::ostream& operator<<(std::ostream&,
const Z<int>::ZZ&)’ does not match any template declaration
error: no match for ‘operator<<’ in ‘std::cout << zz’

0x模式下第二条错误信息不同,但含义相同。

有没有可能做我想做的事?

EDIT 显然,这里有一个 non-deduced context 的实例,它解释了错误消息。然而,问题仍然存在:我可以为嵌套在类模板中的类提供一个有效的 operator&lt;&lt; 吗?

【问题讨论】:

  • 我正在处理它...您可以通过添加“运算符 ”来消除第一个错误,这也使事情更具可读性。
  • @Matthieu:我很想看看那个链接中的代码适应了这个问题。可以把它作为像我这样的“普通凡人”(可能还有“n.m.”)的答案吗? ;-)
  • 您始终可以使用朋友声明正确地实现它。 (是的,我假设您期望得到更好的答案,但我很想知道需要做什么)
  • 这里已经讨论过同样的问题stackoverflow.com/questions/4092237/…

标签: c++ templates


【解决方案1】:

这是函数的一般问题:

template <typename C>
void func(typename C::iterator i);

现在,如果我调用func(int*),我应该使用C 的哪个值?

一般来说,你不能向后工作!许多不同的C 可能已经定义了一个内部类型iterator,它恰好是一组参数的int*

在你的情况下,你的情况有点复杂:

template <typename T>
void func(typename Z<T>::ZZ const&);

但基本上这是同一个问题,Z&lt;T&gt; 是一个模板,而不是一个完整的类,并且您要求为此模板的内部类型 ZZ 创建一个函数。

假设我这样做:

template <typename T>
struct Z { typedef T ZZ; };

template <typename T>
struct Z<T const> { typedef T ZZ; };

注意:典型的迭代器,value_type 不是 const 限定的

那么,在调用func(int)时,我应该使用Z&lt;int&gt;还是Z&lt;int const&gt;

这是不可演绎的。

因此整个事情被称为不可演绎的上下文,标准禁止它,因为没有合理的答案。

经验法则:对函数参数中的typename持怀疑态度。

注意:如果另一个参数已经确定了类型,它们是可以的,例如typename C::iterator find(C&amp;, typename C::const_reference);,因为一旦推断出C,那么C::const_reference 可以毫无问题地使用

【讨论】:

  • 那么有什么解决方法吗?我想要operator&lt;&lt; 用于我的嵌套类,有没有办法定义它?除了在朋友声明中直接内联定义。
  • @n.m:不,没有,只要类保持嵌套。一个选项是在模板Z 之外定义类(例如在details 命名空间中)以及它的operator&lt;&lt;,然后在Z 中使用typedef
  • @Matthieu 一个选项是依赖 CRTP。请参阅我的答案以获得解释。不如朋友的方式,但如果不能使用,可以尝试一下:)
【解决方案2】:

除了友元声明与操作符模板不匹配的问题(或许可以修复为)

class ZZ {
    template<class U>
    friend std::ostream& operator<<(std::ostream& os, const ZZ&);
};

您还遇到了“非推断上下文”的问题,这就是 Matthieu 所链接的内容。

在这个模板中

template<typename T> 
std::ostream& operator<< (std::ostream& os, const typename Z<T>::ZZ&) {
    return (os << "ZZ!");
}

编译器无法确定 T's you 参数将匹配的内容。如果您专注于某些类型,可能会有多个匹配项

template<>
class Z<long>
{
public:
    typedef double   ZZ;
};

template<>
class Z<bool>
{
 public:
    typedef double   ZZ;
};

现在,如果我尝试打印 doubleT 可能是 boollong

如果不检查所有个可能的T,编译器就无法确定这一点,而且它不必这样做。它只是跳过您的运算符。

【讨论】:

  • 有没有办法解决这个问题?我可以在朋友声明中定义operator&lt;&lt; inline,一切似乎都正常。但是如果由于某种原因我不喜欢内联定义,还有其他方法吗?
  • @n.m. - 我不认为有更好的方法。如果您在每个类中定义运算符,您就可以解决处理任何可能的特化的问题。无论我们喜欢与否,模板都非常具有内联函数的标题。 :-(
【解决方案3】:

Matthieu 很好地解释了这个问题,但是在这种情况下可以使用一个简单的解决方法。 您可以使用friend声明在类中实现friend函数:

#include <iostream>

template <typename T> class Z {
public:
  class ZZ {
    friend std::ostream& operator<< (std::ostream& os, const ZZ&) {
      return os << "ZZ!";
    }
  };
};


int main () {
  Z<int>::ZZ zz;
  std::cout << zz << std::endl;
}

【讨论】:

    【解决方案4】:

    另一种技术是提供一个像钩子一样工作的离线类模板。

    template <typename T> class ZZZ { 
      T &getDerived() { return static_cast<T&>(*this); }
      T const &getDerived() const { return static_cast<T const&>(*this); }
    
    protected:
      ~ZZZ() { }
    };
    
    template <typename T> class Z {
      public:
        class ZZ : public ZZZ<ZZ> {
        };
    };
    
    template<typename ZZ> 
    std::ostream& operator<< (std::ostream& os, const ZZZ<ZZ> &zzz) {
        ZZ const& zz = zzz.getDerived();
        /* now you can use zz */
        return (os << "ZZ!");
    }
    

    不过最好使用友元函数定义。但很高兴了解替代方案。

    【讨论】:

    • 不错的技巧,更通用,并且很好地回避了我的范围泄漏:)
    猜你喜欢
    • 2019-03-23
    • 2019-01-29
    • 2019-02-06
    • 1970-01-01
    • 2010-12-10
    • 2019-12-21
    • 1970-01-01
    • 2011-05-04
    • 1970-01-01
    相关资源
    最近更新 更多