Перейти к основному содержимому

Специализация шаблона и перегрузок операторов

· 5 мин. чтения
Дмитрий Аладин
Преподаватель

В данной статье разобран способ реализации перегрузки внешними функциями относительно шаблона класса, а также её специализацию. Способ демонстрируется на примере перегрузки оператора <<.

Исходный проект

Предположим, что имеется проект, состоящий из файла main.cpp.

#include <iostream>

template <class T>
class Example {
T data;

public:
Example() : data{} {}
Example(T data_t) : data{data_t} {}

friend std::ostream& operator<<(std::ostream& out, const Example& obj) {
out << "General output: " << obj.data;
return out;
}
};

int main() {
Example<int> ex(1);
std::cout << ex << std::endl;
}

В консоли при запуске данного проекта будет выведен ожидаемый результат работы дружественной перегрузки оператора <<:

General output: 1

Задача

Требуется сделать специализацию вывода в консоль шаблонного класса для типов int.

Решение №1 (неправильное)

Первое (неправильное) решение, которое может прийти на ум, может выглядеть следующим образом:

#include <iostream>

template <class T>
class Example {
T data;

public:
Example() : data{} {}
Example(T data_t) : data{data_t} {}

friend std::ostream& operator<<(std::ostream& out, const Example& obj) {
out << "General output: " << obj.data;
return out;
}
};

template <>
std::ostream& operator<<(std::ostream& out, const Example<int>& obj) {
out << "General output: " << obj.data;
return out;
}

int main() {
Example<int> ex(1);
std::cout << ex << std::endl;
}

В этом случае будут две ошибки компиляции. Первая, о проблеме поиска специализации оператора <<:

main.cpp:18:15: error: no function template matches function template specialization 'operator<<'
[build] std::ostream& operator<<(std::ostream& out, const Example<int>& obj) {

Вторая, о доступе к приватному полю data класса Example:

main.cpp:19:36: error: 'data' is a private member of 'Example<int>'
[build] out << "General output: " << obj.data;

Данные ошибки указывают на то, что мы неправильно реализуем дружественность для шаблонов.

Решение №2 (правильное)

Правильным решением поставленной задачи будет реализовать перегрузку оператора << в виде шаблона и сделать для неё специализацию для типа int.

Шаг 1. Изучение способа создания шаблона перегрузки

Для правильного решения задачи требуется обратиться к документации. Обратим внимание на раздел "дружественный шаблон":

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

Пример, который демонстрируется в документации, выглядит следующим образом:

class A
{
template<typename T>
friend class B; // every B<T> is a friend of A

template<typename T>
friend void f(T) {} // every f<T> is a friend of A
};

В разделе "Шаблонные операторы-друзья" демонстрируется пример того, как мы можем корректно реализовать перегрузку для дружественных операторов к шаблону. Исходный файл проекта из примера:

#include <iostream>

template<typename T>
class Foo
{
public:
Foo(const T& val) : data(val) {}
private:
T data;

// generates a non-template operator<< for this T
friend std::ostream& operator<<(std::ostream& os, const Foo& obj)
{
return os << obj.data;
}
};

int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}

С правильной реализованным шаблонном перегрузки:

#include <iostream>

template<typename T>
class Foo; // forward declare to make function declaration possible

template<typename T> // declaration
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template<typename T>
class Foo
{
public:
Foo(const T& val) : data(val) {}
private:
T data;

// refers to a full specialization for this particular T
friend std::ostream& operator<< <> (std::ostream&, const Foo&);

// note: this relies on template argument deduction in declarations
// can also specify the template argument with operator<< <T>"
};

// definition
template<typename T>
std::ostream& operator<<(std::ostream& os, const Foo<T>& obj)
{
return os << obj.data;
}

int main()
{
Foo<double> obj(1.23);
std::cout << obj << '\n';
}

к сведению

Обратите внимание на то, что:

  1. Перегрузка оператора <<, как и требует в стандарт, реализуется только внешними функциями относительно рассматриваемого класса.
  2. Сама перегрузка оператора << делается в виде шаблона.

Шаг 2. Создание специализации шаблона перегрузки

Исходя из изученного материала выше, итоговое решение задачи может выглядеть следующим образом:

#include <iostream>

template <class T>
class Example {
T data;

public:
Example() : data{} {}
Example(T data_t) : data{data_t} {}

template <class F>
friend std::ostream& operator<<(std::ostream& out, const Example<F>& obj);
};

template <class F>
std::ostream& operator<<(std::ostream& out, const Example<F>& obj) {
out << "General output: " << obj.data;
return out;
}

template <>
std::ostream& operator<<(std::ostream& out, const Example<int>& obj) {
out << "Int output: " << obj.data;
return out;
}

int main() {
Example<int> ex(1);
std::cout << ex << std::endl;
}

Обратите внимание на то, что:

  1. Объявление перегрузки шаблона оператора << можно сделать внутри шаблона класса. Этот способ отличается от рассмотренного примера шаблона перегрузки оператора << выше.
  2. В силу п.1, чтобы указать отношение дружественности шаблона перегрузки оператора << к рассматриваемому классу, сигнатуру перегрузки требуется писать вместе с ключевым словом template с перечислением параметров шаблона. При этом названия параметров рассматриваемого шаблона должны не совпадать с названиями параметров шаблона сигнатуры перегрузки.
  3. Шаблон перегрузки и её специализация указаны после объявления и определения рассматриваемого класса.