王朝网络
分享
 
 
 

befriending a template in another namespace

王朝vc·作者佚名  2006-01-08
宽屏版  字体: |||超大  

// 这是我在cpptips网站的邮件列表看到, 收藏先! :-)

TITLE: befriending a template in another namespace

(Source: comp.lang.c++.moderated, 6 Oct 2002)

---------------------------------------------------------------------------------

Clarification: the code in a C++ tip does not imply that I think the code

exhibits good programming practice or is of production quality, only that

there is worthwhile technical content. For past tips, see

http://cpptips.hyperformix.com -Allan

"Need help meeting a development deadline? See our corporate sponsor ProSpring

Technical Staffing at http://www.prospring.net"

---------------------------------------------------------------------------------

[Snip]

AUTHOR: Herb Sutter (www.gotw.ca)

There have been several answers to this question, but they're not quite

right. This is an interesting question, so let me try to treat in a little

more depth.

Befriending Templates

---------------------

Say we have a function template that does SomethingPrivate() to the objects

it operates on. In particular, consider the boost::checked_delete() function

template, which deletes the object it's given -- among other things, it

invokes the object's destructor:

namespace boost {

template<typename T> void checked_delete( T* x ) {

// ... other stuff ...

delete x;

}

}

Now, say you want to use this function template with a class where the

operation in question (here the destructor) happens to be private:

// Example 1: No friends

//

class Test {

~Test() { } // private!

};

Test* t = new Test;

boost::checked_delete( t ); // ERROR: Test's destructor is private,

// so checked_delete can't call it.

The solution is dead simple: Just make checked_delete() a friend of Test.

(The only other option is to give up and make Test's destructor public.)

The reason that I'm writing an article about it is because, alas,

befriending a template in another namespace is easier said than done:

- The Bad News: There are two perfectly good standards-conforming

ways to do it, and neither one works on all current compilers.

- The Good News: One of the perfectly good standards-conforming ways

does work on every current compiler I tried except for gcc.

The Original Attempt

--------------------

Here's the original code:

Stephan Born <stephan.born@bNeOuSsPeAnM.de> wrote [edited]:

// Example 2: One way to grant friendship (?)

//

class Test {

~Test() { }

friend void boost::checked_delete( Test* x );

};

Alas, this code didn't work on the poster's compiler (VC++ 6.0). In fact, it

fails on quite a few compilers. In brief, Example 2's friend declaration:

- is technically legal but relies on a dark corner of the language

- is rejected by many current compilers, including very good ones

- is easily fixed to not rely on dark corners and work on all but one

current compiler (gcc)

Why It's Legal but Dark

-----------------------

When declaring friends, there are four options (enumerated in the C++

standard, clause 14.5.3). They boil down to this:

When you declare a friend without saying the keyword "template" anywhere:

1. IF the name of the friend looks like the name of a template

specialization with explicit template arguments (e.g., Name<SomeType>)

THEN the friend is the indicated specialization of that template

2. ELSE IF the name of the friend is qualified with a class or namespace

name (e.g., Some::Name) AND that class or namespace contains a

matching non-template function

THEN the friend is that function

3. ELSE IF the name of the friend is qualified with a class or namespace

name (e.g., Some::Name) AND that class or namespace contains a

matching function template (deducing appropriate template parameters)

THEN the friend is that function template specialization

4. ELSE the name must be unqualified and declare (or redeclare) an

ordinary (non-template) function.

Clearly #2 and #4 only match nontemplates, so to declare the template

specialization as a friend we have two choices: Write something that puts us

into bucket #1, or write something that puts us into bucket #3. In our

example, the options are:

// The original code, legal because it falls into bucket #3

//

friend void boost::checked_delete( Test* x );

or

// Adding "<Test>", legal because it falls into bucket #1

//

friend void boost::checked_delete<Test>( Test* x );

The first is shorthand for the second... but ONLY IF the name is qualified

(here by "boost::") AND there's no matching nontemplate function in the same

indicated scope. This dark corner of the friend declaration rules is

sufficiently surprising to people -- and to most current compilers! -- that

I will propose no fewer than three reasons to avoid using it.

Why To Avoid Bucket #3

----------------------

There are several reasons to avoid bucket #3, even though it's technically

legal:

1. Bucket #3 doesn't always work.

As noted above, it's a shorthand for explicitly naming the template

arguments in angle brackets, but the shorthand works only if the name is

qualified and the indicated class or namespace does not also contain a

matching nontemplate function.

In particular, if the namespace has (or later gets) a matching nontemplate

function, that would get chosen instead because the presence of a

nontemplate function means bucket #2 preempts #3. Kind of subtle and

surprising, isn't it? Kind of easy to mistake, isn't it? Let's avoid such

subtleties.

2. Bucket #3 is a really edgy case, fragile and surprising to most PEOPLE

reading your code.

For example, consider this very slight variant -- all that I've changed is

to remove the qualification "boost":

// Variant: Make the name unqualified

//

class Test {

~Test() { }

friend void checked_delete( Test* x ); // OUCH: Legal, but not what you

}; // want. More about this later.

If you omit "boost::" (i.e., if the call is unqualified), you fall into a

completely different bucket, namely #4 which cannot match a function

template at all, ever, not even with pretty please. I'll bet you dollars to

donuts that just about everyone on our beautiful planet will agree with me

that it's Pretty Surprising that just omitting a namespace name changes the

meaning of the friend declaration so drastically. Let's avoid such edgy

constructs.

3. Bucket #3 is a really edgy case, fragile and surprising to most COMPILERS

reading your code.

Let's try the two options, bucket #1 and bucket #3, on a wide range of

current compilers and see what they think. Will the compilers understand the

standard as well as we do (having read the above)? Will at least all the

strongest compilers do what we expect? No, and no, respectively.

Let's try bucket #3 first:

// Example 2 again

//

namespace boost {

template<typename T> void checked_delete( T* x ) {

// ... other stuff ...

delete x;

}

}

class Test {

~Test() { }

friend void boost::checked_delete( Test* x ); the // original code

};

<richard-dawson-voice>

Survey SAAAAAYS:

</richard-dawson-voice>

Borland 5.5 OK

Comeau 4.3.01 OK

EDG 3.0.1 OK

gcc 2.95.3 ERROR `boost::checked_delete(Test *)' should have

been declared inside `boost'

gcc 3.1.1 ERROR `void boost::checked_delete(Test*)' should

have been declared inside `boost'

gcc 3.2 ERROR `void boost::checked_delete(Test*)' should

have been declared inside `boost'

Intel 6.0.1 OK

Metrowerks 8.2 ERROR friend void boost::checked_delete( Test* x );

name has not been declared in namespace/class

MS VC++ 6.0 ERROR nonexistent function 'boost::checked_delete'

specified as friend

MS VC++ 7.0 OK

MS VC++ 7.1beta ERROR 'boost::checked_delete' : not a function

For MS VC++ 6.0, the error is what the original poster reported. But you'll

get the same (or similar) error on some pretty strong and conformant

compilers, including Metrowerks 8.2, g++ 3.2, and MS VC++ 7.1. (All of these

versions were released, or went into beta, in the past month or two.)

By the way, it shouldn't surprise us that Comeau, EDG, and Intel all agree,

because they're all based on the EDG C++ language implementation. If we

collapse the list so we count only distinct compiler implementations (i.e.,

all of the ones that use EDG are the same code base), and take the latest of

each, it looks more like this:

Borland OK

EDG-based compilers OK

gcc (all versions) ERROR

Metrowerks ERROR

MS VC++ ERROR

So, in short, most C++ language implementations don't accept this version.

Let's try writing it the other standards-conforming way, for bucket #1:

// Example 3: The other way to declare friendship

//

namespace boost {

template<typename T> void checked_delete( T* x ) {

// ... other stuff ...

delete x;

}

}

class Test {

~Test() { }

friend void boost::checked_delete<Test>( Test* x );

};

<richard-dawson-voice>

Survey SAAAAAYS:

</richard-dawson-voice>

Borland 5.5 OK

Comeau 4.3.01 OK

EDG 3.0.1 OK

gcc 2.95.3 ERROR `boost::checked_delete(Test *)' should have

been declared inside `boost'

gcc 3.1.1 ERROR `void boost::checked_delete(Test*)' should

have been declared inside `boost'

gcc 3.2 ERROR `void boost::checked_delete(Test*)' should

have been declared inside `boost'

Intel 6.0.1 OK

Metrowerks 8.2 OK

MS VC++ 6.0 ERROR nonexistent function 'boost::checked_delete'

specified as friend

MS VC++ 7.0 OK

MS VC++ 7.1beta OK

If we collapse the list so that we count only distinct compiler

implementations (i.e., all of the ones that use EDG are the same code base),

and take the latest of each, it looks more like this:

Borland OK

EDG-based compilers OK

gcc (all versions) ERROR

Metrowerks OK

MS VC++ 7.0 & higher OK

Bucket #1 sure feels safer -- this works on every current compiler except

gcc, and every older compiler except MS VC++ 6.0. (This might not be the

most useful answer for the OP, who is using MS VC++ 6.0 which offers no way

to declare the friend; that is why the options for him are to make the

destructor public, or to add "<Test>" and use MS VC++ 7.x.)

Aside: It's the Namespace That's Confusing Them

-----------------------------------------------

Note that if the function template we're trying to befriend wasn't in a

different namespace, then we could use bucket #1 correctly today on all

these compilers:

// Example 4: If only checked_delete weren't in a namespace...

//

template<typename T> void checked_delete( T* x ) { // no longer in boost::

// ... other stuff ...

delete x;

}

class Test {

friend void checked_delete<Test>( Test* x ); // no longer need "boost::"

};

<richard-dawson-voice>

Survey SAAAAAYS:

</richard-dawson-voice>

Borland 5.5 OK

Comeau 4.3.01 OK

EDG 3.0.1 OK

gcc 2.95.3 OK

gcc 3.1.1 OK

gcc 3.2 OK

Intel 6.0.1 OK

Metrowerks 8.2 OK

MS VC++ 6.0 ERROR (emits a syntax error, just can't handle it)

MS VC++ 7.0 OK

MS VC++ 7.1beta OK

So the problem on most compilers that can't handle Example 2 is specifically

declaring friendship for a function template specialization _in another

namespace_. (Whew. Say that three times fast.) Alas, the poster's compiler,

MS VC++ 6.0, can't handle even this simpler case.

Summary

-------

The above demonstrates a pretty high portability price to pay for not just

writing "<Test>" as in Example 3.

Guideline: When you make a function template specialization a friend

of a class template, always explicitly write the function

template specialization's name including the template

arguments. For example:

namespace boost {

template<typename T> void checked_delete( T* x ) { }

}

class Test {

friend void boost::checked_delete ( Test* x ); // BAD

friend void boost::checked_delete<Test>( Test* x ); // GOOD

};

If your compiler doesn't allow either of these legal alternatives for the

friend declaration yet, however, you'll have to make the necessary

function(s) public -- but add a comment saying why, and make a note to

change it back to private as soon as you upgrade your compiler.[1]

Coda: Notes About Previous Responses

------------------------------------

One of the previously posted responses said the following:

>yes, its a bug. get along with a using:

Yes, it is a bug -- in most current compilers, including highly-regarded

ones. It's a dark corner, and we shouldn't rely on it.

The code proposed by the above respondent boils down to writing a

using-declaration (or equivalently a using-directive) and making the call

unqualified:

namespace boost {

template<typename T> void checked_delete( T* x ) { }

}

using boost::checked_delete; // must appear here, not in Test

// "using namespace boost;" works too

class Test {

friend void checked_delete( Test* x ); // unqualified call -- but NOT

}; // the template specialization!

The above friend declaration falls into bucket #4 above:

4. ELSE the name must be unqualified and declare (or redeclare) an

ordinary (non-template) function.

Yes, it is indeed unqualified. But it doesn't mean what that poster thought

it means (and in fact it works equally well without the "using"): What the

friend declaration is in fact doing is declaring a brand-new ordinary

non-template function at the enclosing namespace scope called

::checked_delete( Test * ).

If you try the above code, many compilers will reject it saying that

checked_delete() hasn't been defined, and all compilers will reject it if

you actually try to make use of the friendship and put a private member

access call into the boost::checked_delete() template.

The other response was:

>As a work-around, try declaring & defining an overloaded

>checked_delete for test.

The suggested code is:

namespace boost {

template<typename T> void checked_delete( T* x ) { }

}

class Test;

void checked_delete( Test* x ) { boost::checked_delete( x ); }

class Test {

friend void checked_delete( Test* x );

};

This is legal code accepted by all the above compilers, but it doesn't do

what the original poster was asking for, to wit: It does not make the

boost::checked_delete<Test>() internals able to call private members of

class Test.

[1] There are other workarounds, but they're all much more cumbersome. For

example, you could create a proxy class inside namespace boost and befriend

that.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有