Как подружиться с шаблонными параметрами
Мэтью Уилсон (Matthew Wilson) в своей статье “Дружественные Шаблоны” (Friendly Templates) которую вы можете найти в оригинале по адресу http://www.cuj.com/documents/s=8943/cujexp0312wilson2/ рассказал о том, как использовать шаблонные параметры в объявлении друзей классов. Его статья послужила толчком для некоторых интересных C++ решений. Ниже представлен перевод статьи Мэтью (немного лишней информации я убрал, однако важная информация от начала и до конца представлена в полном объеме) и мои исправления/дополнения.
Статья Мэтью Уилсона.
Иногда возникают ситуации, когда необходимо объявить друга класса, однако его тип представлен в виде шаблонного параметра. Например:
// Пример #1
template <typename T>
class Thing
{
friend T; // Разрешает классу T доступ ко всем данным и методам данного класса.
private:
int value_;
};
Выглядит вполне разумно, не так ли? Но это не стандартный C++. В стандарте языка C++ (документ C++-98 Standard (ISO/IEC 14882)), пункт 7.1.5.3(2) сказано, что внутри шаблонного класса с параметром типа T конструкция вида “friend class T;” запрещена (http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1520.pdf).
Но я прагматист, а не правовед языка. То, что я хочу сделать – вполне логично, как минимум в тех местах кода, где это просто необходимо, поэтому я заинтересован в таком решении, пусть оно и нестандартно.
Пример #1, приведенный выше, обрабатывается такими компиляторами как: Borland (5.51 and 5.6), Comeau (4.3.0.1), Digital Mars (GCC (2.95), Intel (6 and 7), Watcom (11 and 12), and Visual C++ (4.2 through 7.1). Однако он не работает с CodeWarrior (7 and 8) или GCC (3.2). С компилятором Comeau код работает в стандартной конфигурации, если же указать флаг “—strict”, то компилятор начнет “ругаться”, что конструкция и все ее возможные формы не являются стандартными.
Шаблонный параметр, по сути, может быть простым типом (POD type), а не классом, например типом “int”. Для некоторых компиляторов - это проблема. Такие типы не могут быть друзьями, поэтому стоит указать, что тип, переданный в качестве шаблонного параметра – это класс. Например:
// Пример #2
template <typename T>
class Thing
{
friend class T; // Разрешает классу T доступ ко всем данным и методам данного класса.
private:
int value_;
};
Этот пример работает без проблем с CodeWarrior, Digital Mars и Watcom компиляторами. Таким образом первый либо второй пример нам подходит в том или ином случае. Однако проблемы до сих пор остаются для компилятора GCC версии 3.2.
Будучи тем, кто сторонится использования “друзей”, я не смог придумать решение этой проблемы и проконсультировался с ребятами из группы comp.lang.c++.moderated (http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&newwindow=1&threadm=5d33192c.0310010048.44c06c13@posting.google.com&rnum=1&prev=), которые посоветовали два следующих примера:
// Пример #3
template <typename T>
class Thing
{
struct friend_maker
{
typedef T T2;
};
typedef typename friend_maker::T2 friend_type;
friend class friend_type;
private:
int value_;
};
// Пример #4
template <typename T>
class Thing
{
template<class T2>
struct friend_maker
{
typedef T2 T3;
};
typedef typename friend_maker<T>::T3 friend_type;
friend class friend_type;
private:
int value_;
};
Таким образом, мы нашли решения, с помощью которых можно использовать шаблонные параметры в качестве друзей класса для всех современных компиляторов. Осталось только привести это к более-менее удобному виду. Ниже приведен код с использованием препроцессора для корректного объявления конструкции в зависимости от компилятора.
// Пример #5
#if defined(__BORLANDC__) || \
defined(__COMO__) || \
defined(__DMC__) || \
( defined(__GNUC__) && \
__GNUC__ < 3) || \
defined(__INTEL_COMPILER) || \
defined(__WATCOMC__) || \
defined(_MSC_VER)
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend T
#elif defined(__MWERKS__)
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend class T
#elif defined(__GNUC__) && \
__GNUC__ >= 3
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) \
struct friend_maker \
{ \
typedef T T2; \
}; \
typedef typename friend_maker::T2 friend_type; \
friend friend_type
#else
// Мы не знаем, какой компилятор используется,
// возможно следует сгенерировать ошибку, однако будем надеяться,
// что он поддерживает такую конструкцию:
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend T
#endif
Теперь мы очень просто можем использовать такой код:
// Пример #6
template <typename T>
class Thing
{
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T);
private:
int value_;
};
Список компиляторов и данные о том, какую форму шаблонных друзей они поддерживают можно посмотреть в оригинальной статье.
Мои исправления и дополнения
Что произойдет, если нам будет необходимо объявить больше одного “друга”? Например вот так:
// Пример #7
template <typename T1, typename T2>
class Thing
{
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T1);
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T2);
private:
int value_;
};
Макрос “DECLARE_TEMPLATE_PARAM_AS_FRIEND” локально объявляет структуру “friend_maker”. В данном случае она будет сгенерирована два раза, но имена отличаться не будут, что приведет к ошибке.
Чтобы избежать такого конфуза я немного переделал макрос для того, чтобы имена структуры были уникальными в зависимости от имени типа “друга”.
// Пример #8
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) \
struct make_friend_##T \
{ \
typedef T T2; \
}; \
typedef typename make_friend_##T::T2 friend_type_##T; \
friend friend_type_##T
Вот и все, теперь можно использовать это решение в своих проектах.
Например, я сделал версию макроса для проекта Boost.
#ifndef BOOST_TEMPLATE_FRIEND_HPP
#define BOOST_TEMPLATE_FRIEND_HPP
#include <boost/config.hpp>
#include <boost/detail/workaround.hpp>
#if defined(BOOST_MSVC) && BOOST_WORKAROUND(BOOST_MSVC, >= 1020)
# pragma once
#endif
#if (defined(__GNUC__) && BOOST_WORKAROUND(__GNUC__, < 3)) || \
defined(__BORLANDC__) || \
defined(__COMO__) || \
defined(__DMC__) || \
defined(__WATCOMC__) || \
defined(BOOST_MSVC) || \
defined(BOOST_INTEL)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend T
#elif defined(__MWERKS__)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend class T
#elif defined(__GNUC__) && BOOST_WORKAROUND(__GNUC__, >= 3)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) \
struct make_friend_##T \
{ \
typedef T T2; \
}; \
typedef typename make_friend_##T::T2 friend_type_##T; \
friend friend_type_##T
#else
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend T
#endif
#endif // BOOST_TEMPLATE_FRIEND_HPP
Иногда возникают ситуации, когда необходимо объявить друга класса, однако его тип представлен в виде шаблонного параметра. Например:
// Пример #1
template <typename T>
class Thing
{
friend T; // Разрешает классу T доступ ко всем данным и методам данного класса.
private:
int value_;
};
Выглядит вполне разумно, не так ли? Но это не стандартный C++. В стандарте языка C++ (документ C++-98 Standard (ISO/IEC 14882)), пункт 7.1.5.3(2) сказано, что внутри шаблонного класса с параметром типа T конструкция вида “friend class T;” запрещена (http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1520.pdf).
Но я прагматист, а не правовед языка. То, что я хочу сделать – вполне логично, как минимум в тех местах кода, где это просто необходимо, поэтому я заинтересован в таком решении, пусть оно и нестандартно.
Пример #1, приведенный выше, обрабатывается такими компиляторами как: Borland (5.51 and 5.6), Comeau (4.3.0.1), Digital Mars (GCC (2.95), Intel (6 and 7), Watcom (11 and 12), and Visual C++ (4.2 through 7.1). Однако он не работает с CodeWarrior (7 and 8) или GCC (3.2). С компилятором Comeau код работает в стандартной конфигурации, если же указать флаг “—strict”, то компилятор начнет “ругаться”, что конструкция и все ее возможные формы не являются стандартными.
Шаблонный параметр, по сути, может быть простым типом (POD type), а не классом, например типом “int”. Для некоторых компиляторов - это проблема. Такие типы не могут быть друзьями, поэтому стоит указать, что тип, переданный в качестве шаблонного параметра – это класс. Например:
// Пример #2
template <typename T>
class Thing
{
friend class T; // Разрешает классу T доступ ко всем данным и методам данного класса.
private:
int value_;
};
Этот пример работает без проблем с CodeWarrior, Digital Mars и Watcom компиляторами. Таким образом первый либо второй пример нам подходит в том или ином случае. Однако проблемы до сих пор остаются для компилятора GCC версии 3.2.
Будучи тем, кто сторонится использования “друзей”, я не смог придумать решение этой проблемы и проконсультировался с ребятами из группы comp.lang.c++.moderated (http://www.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&newwindow=1&threadm=5d33192c.0310010048.44c06c13@posting.google.com&rnum=1&prev=), которые посоветовали два следующих примера:
// Пример #3
template <typename T>
class Thing
{
struct friend_maker
{
typedef T T2;
};
typedef typename friend_maker::T2 friend_type;
friend class friend_type;
private:
int value_;
};
// Пример #4
template <typename T>
class Thing
{
template<class T2>
struct friend_maker
{
typedef T2 T3;
};
typedef typename friend_maker<T>::T3 friend_type;
friend class friend_type;
private:
int value_;
};
Таким образом, мы нашли решения, с помощью которых можно использовать шаблонные параметры в качестве друзей класса для всех современных компиляторов. Осталось только привести это к более-менее удобному виду. Ниже приведен код с использованием препроцессора для корректного объявления конструкции в зависимости от компилятора.
// Пример #5
#if defined(__BORLANDC__) || \
defined(__COMO__) || \
defined(__DMC__) || \
( defined(__GNUC__) && \
__GNUC__ < 3) || \
defined(__INTEL_COMPILER) || \
defined(__WATCOMC__) || \
defined(_MSC_VER)
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend T
#elif defined(__MWERKS__)
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend class T
#elif defined(__GNUC__) && \
__GNUC__ >= 3
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) \
struct friend_maker
{
typedef T T2; \
};
typedef typename friend_maker::T2 friend_type; \
friend friend_type
#else
// Мы не знаем, какой компилятор используется,
// возможно следует сгенерировать ошибку, однако будем надеяться,
// что он поддерживает такую конструкцию:
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T) friend T
#endif
Теперь мы очень просто можем использовать такой код:
// Пример #6
template <typename T>
class Thing
{
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T);
private:
int value_;
};
Список компиляторов и данные о том, какую форму шаблонных друзей они поддерживают можно посмотреть в оригинальной статье.
Мои исправления и дополнения
Что произойдет, если нам будет необходимо объявить больше одного “друга”? Например вот так:
// Пример #7
template <typename T1, typename T2>
class Thing
{
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T1);
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T2);
private:
int value_;
};
Макрос “DECLARE_TEMPLATE_PARAM_AS_FRIEND” локально объявляет структуру “friend_maker”. В данном случае она будет сгенерирована два раза, но имена отличаться не будут, что приведет к ошибке.
Чтобы избежать такого конфуза я немного переделал макрос для того, чтобы имена структуры были уникальными в зависимости от имени типа “друга”.
// Пример #8
# define DECLARE_TEMPLATE_PARAM_AS_FRIEND(T)
struct make_friend_##T
{
typedef T T2;
};
typedef typename make_friend_##T::T2 friend_type_##T; \
friend friend_type_##T
Вот и все, теперь можно использовать это решение в своих проектах.
Например, я сделал версию макроса для проекта Boost.
#ifndef BOOST_TEMPLATE_FRIEND_HPP
#define BOOST_TEMPLATE_FRIEND_HPP
#include <boost/config.hpp>
#include <boost/detail/workaround.hpp>
#if defined(BOOST_MSVC) && BOOST_WORKAROUND(BOOST_MSVC, >= 1020)
# pragma once
#endif
#if (defined(__GNUC__) && BOOST_WORKAROUND(__GNUC__, < 3)) || \
defined(__BORLANDC__) || \
defined(__COMO__) || \
defined(__DMC__) || \
defined(__WATCOMC__) || \
defined(BOOST_MSVC) || \
defined(BOOST_INTEL)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend T
#elif defined(__MWERKS__)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend class T
#elif defined(__GNUC__) && BOOST_WORKAROUND(__GNUC__, >= 3)
# define BOOST_DECLARE_TEMPLATE_FRIEND(T)
struct make_friend_##T
{
typedef T T2;
};
typedef typename make_friend_##T::T2 friend_type_##T; \
friend friend_type_##T
#else
# define BOOST_DECLARE_TEMPLATE_FRIEND(T) friend T
#endif
#endif // BOOST_TEMPLATE_FRIEND_HPP
