BYTE.com > Flexible C++
Print Friendly Version
By Matthew Wilson
December, 2003
(Flexible C++ #5: Friendly Templates
Friendly Templates
by Matthew Wilson
In "Befriending Templates," Herb Sutter gives a great treatise on the subject of template friendship from the perspective of granting friendship from classes to template functions. In this column, I want to look at the subject from the different perspective of granting friendship from a template to one of the template's parameterizing types, as in:
// form #1
template <typename T>
class Thing
{
friend T; // Allow T to see inside Thing<T>
private:
int m_value;
};
Seems like an reasonable thing to do, does it not? Alas, it is not legal C++. According to the C++-98 Standard (ISO/IEC 14882), 7.1.5.3(2): "...within a class template with a template type-parameter T, the declaration ["]friend class T;["] is ill-formed" (http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1520.pdf).
However, I'm a pragmatist, not a language lawyer. What I want to do is sensible, at least in the limited circumstances in which I want to do it, so I'm not terribly interested in its illegality. I just want to get it to work with all the compilers I need.
The form just presented, which I'll call form #1, works with compilers such as 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). It does not work with CodeWarrior (7 and 8) or GCC (3.2). Comeau works with this form, and all others, when in its Win32 default configuration. In strict mode (--strict) it does not work with any, reflecting the fact that the technique in all of its forms is not legal C++.
Since T is a classwe're granting it friendship, so it can't exactly be an intmaybe we should mention that fact to the compiler, as in:
// form #2
template <typename T>
class Thing
{
friend class T; // Allow T to see inside Thing<T>
private:
int m_value;
};
This is form #2. CodeWarrior, Digital Mars, and Watcom support this form. So a bit of compiler discrimination between forms #1 and #2 would cover most bases, but we're still not satisfying GCC 3.2.
Being someone that avoids friendship like the plague, I ran out of experience pretty soon here, so I consulted the newsgroup gurus. The kind people of comp.lang.c++.moderated were very helpful, and suggested two versions, forms #3 and #4:
// form #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 m_value;
};
// form #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 m_value;
};
The complication here is we're back to the inconsistency seen in forms #1 and #2 between whether the class specifier should be used in the class declaration. GCC and Visual C++ both require that class is not used, whereas the other compilers require that it is used. Hence, the real versions of forms #3 and #4 require some further discrimination, as in:
#if defined(__BORLANDC__) || \
defined(__COMO__) || \
defined(__DMC__) || \
defined(__INTEL_COMPILER) || \
defined(__MWERKS__) || \
defined(__WATCOMC__)
# define TEMPLATE_FRIEND_FRIEND_MAKER_FRIEND_DECL_USES_CLASS
#elif defined(__GNUC__) || \
defined(_MSC_VER)
/* Not defined */
#else /* ? compiler */
# error Other compilers not discriminated ...
#endif /* compiler */
// form #3
template <typename T>
class Thing
{
struct friend_maker
{
typedef T T2;
};
typedef typename friend_maker::T2 friend_type;
#ifdef TEMPLATE_FRIEND_FRIEND_MAKER_FRIEND_DECL_USES_CLASS
friend class friend_type;
#else /* ? TEMPLATE_FRIEND_FRIEND_MAKER_FRIEND_DECL_USES_CLASS */
friend friend_type;
#endif /* TEMPLATE_FRIEND_FRIEND_MAKER_FRIEND_DECL_USES_CLASS */
private:
int m_value;
};
Since no compiler I use rejects form #3 and accepts form #4, I just stick with form #3. Table 1 summarizes the support.
Table 1: Friendship support of various compilers
Compiler |
Form #1 |
Form #2 |
Form #3 |
Form #4 |
Borland C++ (5.51 & 5.6) |
Yes |
|
|
|
CodeWarrior (7 and 8) |
|
Yes |
Yes |
Yes |
Comeau (4.3.0.1) |
non-strict only |
non-strict only |
non-strict only |
non-strict only |
Digital Mars (8.26-8.37) |
Yes |
Yes |
Yes |
Yes |
GCC 2.95 |
Yes |
|
|
|
GCC 3.2 |
|
Yes |
Yes |
|
Intel (6 and 7) |
Yes |
Yes |
Yes |
Yes |
Visual C++ (4.2 - 7.1) |
Yes |
|
Yes |
Yes, except 4.2 |
Watcom (11 and 12) |
Yes |
Yes |
Yes |
|
* Comeau provides, from version 4.3.3 onwards, the --friendT
command-line option, which allows Friendly Templates even when the
--strict option is specified.
So there you have it. The language says it's illegal, so it's not surprising
that there's a fair degree of variance in the compilers' "illegal" support. I really don't like macros as a rule, especially ones that "generate" code,
but in this case it's probably necessary. Hence, we can define:
#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
#endif /* compiler */
It is then used as follows:
// form #2
template <typename T>
class Thing
{
DECLARE_TEMPLATE_PARAM_AS_FRIEND(T);
private:
int m_value;
};
I've defined the macro so that it needs a terminating semi-colon in the code where it is used. You may choose to do it otherwise.
Acknowledgments
Many thanks to the helpful residents of comp.lang.c++.moderated. In particular, thanks to Attila Feher, Gabriel Dos Reis, Ron Crane, and "tom_usenet" for providing explanations of why it's illegal. And to John Potter for offering form #3, and Steven Keuchel for offering form #4.
Comeau Update – new!
Since the original form of this article was published I’ve been in
contact – he might call it badgering, I couldn’t possibly comment
;-)– with Greg Comeau, and as of version 4.3.3 the Comeau compiler supports
the new command-line option --friendT, which means you can have strict
compilation but still have friendly templates. This is great news, as I
have big plans for this technique; more of that at another time ….
Anyway, many thanks to Comeau Computing for being such a responsive vendor.
A word of caution: using non-standard behaviour is a bad thing, and not
to be taken lightly. The point here is that the technique is a desirable
one, that almost all compilers support it, and that even Comeau,
arguably the most compliant compiler in the business, now supports it
with the --friendtT option. I think in this specific case that there is
sufficient momentum in its support that we can safely hitch ourselves to
this train. However, there are no guarantees in non-standard behaviour,
and if you do not share my level of confidence in its ongoing wide
support you should not use the technique.
About the Author
Matthew Wilson is a software development consultant for Synesis Software, specializing in robustness and performance in C, C++, Java, and .NET. Matthew is the author of the STLSoft libraries, and the forthcoming book Imperfect C++ (to be published by Addison-Wesley, 2004). He can be contacted via matthew@synesis.com.au or at http://stlsoft.org/.
BYTE.com > Flexible C++
|