оператор << (ostream &, X) для класса X, вложенного в шаблон класса - PullRequest
15 голосов
/ 04 июля 2011

Этот компилируется и работает так, как должен (не вложенный шаблон):

#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 второе сообщение об ошибке отличается, но смысл тот же.

Можно ли делать то, что я хочу?

РЕДАКТИРОВАТЬ По-видимому, здесь есть экземпляр невведенного контекста , который объясняет сообщения об ошибках. Вопрос, однако, все еще стоит: могу ли я иметь рабочий operator<< для класса, вложенного в шаблон класса?

Ответы [ 4 ]

7 голосов
/ 04 июля 2011

Это общая проблема для функций:

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<T> - это шаблон, а не полный класс, и вы просите создать функцию для внутреннего типа ZZ этого шаблона.

Предположим, я делаю:

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

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

Примечание: типично для итераторов, value_type не является константным

Тогда, при вызове func(int), я должен использовать Z<int> или Z<int const>?

Не подлежит вычету.

И, таким образом, все это называется не выводимым контекстом , и Стандарт запрещает это, потому что нет разумного ответа.

Полезное правило: с подозрением относитесь к typename в параметрах функции.

Примечание: они в порядке, если другой аргумент уже зафиксировал тип, например typename C::iterator find(C&, typename C::const_reference);, поскольку, как только C выводится, тогда C::const_reference может использоваться без проблем

4 голосов
/ 04 июля 2011

Помимо проблемы, что объявление друга не соответствует шаблону оператора (возможно, это можно исправить как)

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

у вас также есть проблема с «не выводимым контекстом», на который ссылается Матье.

В этом шаблоне

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;
};

Теперь, если я попытаюсь напечатать double, T может быть либо bool, либо long.

Компилятор не может знать это наверняка, не проверив всех возможных Т, и он не должен этого делать. Вместо этого он просто пропускает вашего оператора.

3 голосов
/ 04 июля 2011

Матье очень хорошо объяснил проблему, но в этом случае можно использовать простой обходной путь. Вы можете реализовать функцию друга внутри класса с помощью объявления друга:

#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;
}
2 голосов
/ 05 июля 2011

Другой метод - предоставить шаблон класса вне линии, который работает как ловушка.

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

Использование определения функции друга предпочтительно.Но хорошо знать об альтернативах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...