operator<<(ostream&, X) for class X nested in a class template

后端 未结 4 1384
终归单人心
终归单人心 2021-01-04 02:52

This one compiles and works like it should (non-nested template):

#include 

template class Z;

template          


        
相关标签:
4条回答
  • 2021-01-04 03:15

    Matthieu explained the problem very well, but a simple work-around can be used in this case. You can implement the friend function inside the class with the friend declaration:

    #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;
    }
    
    0 讨论(0)
  • 2021-01-04 03:15

    Apart from the problem that the friend declaration doesn't match the operator template (perhaps fixable as)

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

    you also have a problem with a "non-deduced context", which is what Matthieu links to.

    In this template

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

    the compiler isn't able to figure out for what T's you parameter will match. There could be several matches, if you specialize for some types

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

    Now if I try to print a double, T could be either bool or long.

    The compiler cannot know this for sure without checking for all possible T's, and it doesn't have to do that. It just skips your operator instead.

    0 讨论(0)
  • 2021-01-04 03:24

    A different technique is to provide an out of line class template which works like a hook.

    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!");
    }
    

    Using a friend function definition is preferable though. But it's good to know about alternatives.

    0 讨论(0)
  • 2021-01-04 03:35

    This is a general problem for functions:

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

    Now, if I call func(int*), which value of C should I use ?

    In general, you cannot work backward ! Many different C could have defined an internal type iterator that happens to be a int* for some set of parameters.

    In your case, you are complicating the situation a bit:

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

    But fundamentally this is the same issue, Z<T> is a template, not a full class, and you are asking to create a function for the inner type ZZ of this template.

    Suppose I do:

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

    Note: typical of iterators, the value_type is not const-qualified

    Then, when invoking func(int), should I use Z<int> or Z<int const> ?

    It is non-deducible.

    And thus the whole thing is referred to as a non-deducible context, and the Standard forbids it because there is no sensible answer.

    Rule of Thumb: be suspicious of typename in the parameters of a function.

    Note: they are OK if another argument already pinned down the type, example typename C::iterator find(C&, typename C::const_reference); because once C is deduced, then C::const_reference may be used without trouble

    0 讨论(0)
提交回复
热议问题