Topic: Core Language Active Issue #329


Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Wed, 15 May 2002 19:55:12 GMT
Raw View
Hyman Rosen:
> Here's an example where the
> suggested change definitely changes the behavior of a
> currently legal program.
>
> //////////////////////////////////////////////////////////////////////////
> #include <algorithm>
> extern "C" int printf(const char *, ...);
>
> template <typename T> struct equal
> { friend bool operator==(const T &l, const T &r) { return l.a == r.a; } };
> template <typename T> struct evil
> { friend void f(const T &) { equal<T>(); } };
>
> struct x
> {
>          int a;
>          operator int() const { return a / 10; }
> };
>
> int main()
> {
>          x xs[1] = { { 10 } };
>          x x1 = { 11 };
>          evil<x>();
>          file://f(x1);
>          if (std::find(xs + 0, xs + 1, x1) == xs + 1)
>                  printf("Used operator==(x,x)\n");
>          else
>                  printf("Used operator==(int,int)\n");
> }
> //////////////////////////////////////////////////////////////////////////
>
> Given the proposed change, the program above would
> use operator==(int,int) unless the call to f(x1) was
> uncommented, whereupon it would switch over
> to using operator==(x,x). Under current rules, it would
> always use operator==(x,x).

It's not that simple. For a start, you will have a tough
time finding a compiler with the standard behaviour
(perhaps EDG-based ones?) I tried C++ Builder 5,
Visual C++ 6 and 7 beta 2 and CodeWarrior 5 and
none of them did it the standard way. None of these
four did it the other way you postulated either: even
with the call to f uncommented they still used
op==(int, int)! If I put a call to equal<x>(); in the main
function all except Visual C++ 6 (which produced a
link error) started to call op==(x, x).

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Wed, 15 May 2002 20:58:54 GMT
Raw View
Garry Lancaster wrote:
> It's not that simple. For a start, you will have a tough
> time finding a compiler with the standard behaviour

It *is* that simple. Lack of correct implementations has
no relevance as to whether something should be changed,
unless perhaps a correct implementation was impossible,
which certainly is not the case here.

g++ 3.0.3 uses operator==(x,x) when the call to f is
uncommented.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Thu, 16 May 2002 19:18:03 GMT
Raw View
Hyman Rosen:
> Lack of correct implementations has
> no relevance as to whether something should be changed,
> unless perhaps a correct implementation was impossible,
> which certainly is not the case here.

On the contrary, since the current standard-mandated
behaviour is scarce in reality, this gives a lot more
freedom to change the standard without affecting
real existing code.

> g++ 3.0.3 uses operator==(x,x) when the call to f is
> uncommented.

What about when the f call is commented? If it was
then using operator(int, int) we can add it to the list
of compilers that don't follow the current rules.

Incidentally, the #329 defect report was submitted by
John Spicer of the Edison Design Group (EDG). According
to him, the EDG compiler front-end actually implements
the behaviour as specified in the standard, which makes
it a rare beast, but even so, he is actually advocating the
change.

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Thu, 16 May 2002 20:53:08 GMT
Raw View
Garry Lancaster wrote:
> On the contrary, since the current standard-mandated
> behaviour is scarce in reality, this gives a lot more
> freedom to change the standard without affecting
> real existing code.

But compiler implementations change much more quickly than
compiler standards. Furthermore, libraries are now being
written that depend upon the most subtle nuances of the C++
standard in order to do template metaprogramming. I still
don't see any reason for changing the rule.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Fri, 17 May 2002 16:39:42 GMT
Raw View
Garry Lancaster wrote:
> > On the contrary, since the current standard-mandated
> > behaviour is scarce in reality, this gives a lot more
> > freedom to change the standard without affecting
> > real existing code.

Hyman Rosen:
> But compiler implementations change much more quickly than
> compiler standards.

And in this respect, according to the defect report, they
are changing *away* from the standard behaviour.

> Furthermore, libraries are now being
> written that depend upon the most subtle nuances of the C++
> standard in order to do template metaprogramming.

Of course, if there were even one serious library that
relied on this behaviour, that would argue against a
change. I don't know of any myself.

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Mon, 13 May 2002 19:28:25 GMT
Raw View
Garry Lancaster wrote:
> Maybe I'm missing something here. Since this
> behaviour is unaltered by the proposed change,
> why is it relevant?

Just to point out that it's not only the definitions that
could cause errors, but declarations as well. This reduces
some of the motivation for allowing unused erroneous
definitions.

> I don't think it does work currently. The function f is
> implicitly inline, remember.

OK, you got me on that one. Here's an example where the
suggested change definitely changes the behavior of a
currently legal program.

//////////////////////////////////////////////////////////////////////////
#include <algorithm>
extern "C" int printf(const char *, ...);

template <typename T> struct equal
{ friend bool operator==(const T &l, const T &r) { return l.a == r.a; } };
template <typename T> struct evil
{ friend void f(const T &) { equal<T>(); } };

struct x
{
         int a;
         operator int() const { return a / 10; }
};

int main()
{
         x xs[1] = { { 10 } };
         x x1 = { 11 };
         evil<x>();
         //f(x1);
         if (std::find(xs + 0, xs + 1, x1) == xs + 1)
                 printf("Used operator==(x,x)\n");
         else
                 printf("Used operator==(int,int)\n");
}
//////////////////////////////////////////////////////////////////////////

Given the proposed change, the program above would use operator==(int,int)
unless the call to f(x1) was uncommented, whereupon it would switch over
to using operator==(x,x). Under current rules, it would always use
operator==(x,x). We now have a proposal whereby simply calling a function
in one part of a program silently changes which function is called by a
completely different part of the program. Is this enough to destroy the
proposal yet?

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Fri, 10 May 2002 03:03:04 GMT
Raw View
Garry Lancaster wrote:
> Hyman Rosen:
>>The declaration itself can involve the template
>>parameters in such a way that it is legal for some
>>types and illegal for others.
>
> Could you explain what you mean by that?

Simple enough -

template<typename T>
struct X
{
 friend void f(typename T::t) { }
};

Whenever you instantiate an X<T>, you have to inject
void f(T::t) into the enclosing namespace. Whether
you inject the declaration or the definition, it's
stiil going to be illegal if the T in question doesn't
have a member type t.

> Perhaps you could give some reasons why the behaviour
> should remain as currently specified?

There's the issue of what happens when two instantiations
cause an ODR violation:

template <int N> struct X { friend int f() { return N; } };
X<1> x1; X<2> x2;

Would this be an error? (It is now.)

You could also break existing programs:

file1.cpp:
template <typename T> struct X { friend void f(T) { }; };
X<int> x;

file2.cpp:
extern void f(int);
int main() { f(3); }

This would fail to link, whereas it would work currently.

Finally, 14.5.3/5 is crystal clear in its description of what
happens when templates define friend functions. I understood
that the issues list was to resolve defects and ambiguities
in the standard, not to change the language.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Mon, 13 May 2002 17:19:29 GMT
Raw View
> > Hyman Rosen:
> >>The declaration itself can involve the template
> >>parameters in such a way that it is legal for some
> >>types and illegal for others.

Garry Lancaster:
> > Could you explain what you mean by that?

Hyman Rosen:
> Simple enough -
>
> template<typename T>
> struct X
> {
> friend void f(typename T::t) { }
> };
>
> Whenever you instantiate an X<T>, you have to inject
> void f(T::t) into the enclosing namespace. Whether
> you inject the declaration or the definition, it's
> stiil going to be illegal if the T in question doesn't
> have a member type t.

Maybe I'm missing something here. Since this
behaviour is unaltered by the proposed change,
why is it relevant?

> > Perhaps you could give some reasons why the behaviour
> > should remain as currently specified?
>
> There's the issue of what happens when two instantiations
> cause an ODR violation:
>
> template <int N> struct X { friend int f() { return N; } };
> X<1> x1; X<2> x2;
>
> Would this be an error?

No, under the proposed change it wouldn't be an error,
because the code doesn't require the definition of f be
instantiated.

> (It is now.)

Yes, it is an error now. Although those compilers that
already behave as per the suggestion don't spot it e.g
C++ Builder 5.

> You could also break existing programs:
>
> file1.cpp:
> template <typename T> struct X { friend void f(T) { }; };
> X<int> x;
>
> file2.cpp:
> extern void f(int);
> int main() { f(3); }
>
> This would fail to link, whereas it would work currently.

I don't think it does work currently. The function f is
implicitly inline, remember.

> Finally, 14.5.3/5 is crystal clear in its description of what
> happens when templates define friend functions. I understood
> that the issues list was to resolve defects and ambiguities
> in the standard, not to change the language.

I agree this isn't really a typical Defect Report. Different
people interpret the word "defect" in slightly different
ways and this one is certainly in the grey area. So,
perhaps it isn't the most unarguable defect ever, but
with C++0x on the horizon I'd hope we could judge
the suggested change on its own merits.

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Thu, 2 May 2002 15:14:55 CST
Raw View
The comment on Core Language Active Issue #329 contains
a suggestion that functions defined in a friend function
declaration in a class template should be evaluated only
if used. But the point of the existing mechanism is to
force these functions into the enclosing namespace so
that they are available for overload resolution. It is
important that any change to this mechanism preserve the
intended behavior.

For example, one can have a template with one type parameter
which defines all the relational operators for the type as
friends using operator<. Then the type itself can inherit from
the template, and the relational operators spring into being
for this type, without affecting any others.

template <typename T> struct relops {
 friend bool operator==(const T &a, const T &b)
 { std::cout << "Friend"; return !(a < b) && !(b < a); }
};
struct me : relops<me>
{
 int x;
 me(int x = 0) : x(x) { }
 operator int() { return x; }
 bool operator<(const me &o) { return x < o.x; }
};
int main()
{
 std::set<me> mine;
 me you(7);
 mine.insert(you);
 // The following should use the operator==(me,me) friend,
 // not the conversion to int followed by operator==(int,int)
 std::find(mine.begin(), mine.end(), you);
}

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Mon, 6 May 2002 07:53:12 GMT
Raw View
Hyman Rosen:
> The comment on Core Language Active Issue #329 contains
> a suggestion that functions defined in a friend function
> declaration in a class template should be evaluated only
> if used. But the point of the existing mechanism is to
> force these functions into the enclosing namespace so
> that they are available for overload resolution. It is
> important that any change to this mechanism preserve the
> intended behavior.
>
> For example, one can have a template with one type parameter
> which defines all the relational operators for the type as
> friends using operator<. Then the type itself can inherit from
> the template, and the relational operators spring into being
> for this type, without affecting any others.
>
> template <typename T> struct relops {
> friend bool operator==(const T &a, const T &b)
> { std::cout << "Friend"; return !(a < b) && !(b < a); }
> };
> struct me : relops<me>
> {
> int x;
> me(int x = 0) : x(x) { }
> operator int() { return x; }
> bool operator<(const me &o) { return x < o.x; }
> };
> int main()
> {
> std::set<me> mine;
> me you(7);
> mine.insert(you);
> // The following should use the operator==(me,me) friend,
> // not the conversion to int followed by operator==(int,int)

Actually it won't use either. The default comparison
operator for std::set is <, not ==. Equality is just
defined as:

comp( lhs, rhs ) == false && comp( rhs, lhs ) == false

> std::find(mine.begin(), mine.end(), you);
> }

For what it's worth I think that the proposed resolution
to #329 is OK. The current rule is counter-intuitive:
member functions are only defined when "referenced
in a context that requires the function definition to
exist" [14.17.1/2] and I don't see why friend functions
should be different.

We need to make the distinction between declaration
and definition. It is right that the declaration is visible
and therefore considered for overload resolution,
even if the function instantiation has not yet been
defined. (Of course, if it is selected to be called, as
the best fit overload, the instantiation will then be
defined.) This should sort out the issue I think you
were getting at.

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Hyman Rosen <hyrosen@mail.com>
Date: Tue, 7 May 2002 15:47:30 GMT
Raw View
Garry Lancaster wrote:
> Actually it won't use either. The default comparison
> operator for std::set is <, not ==.

I'm calling std::find on a sequence.
25.1.2 says that operator== is used.

> We need to make the distinction between declaration
> and definition. It is right that the declaration is visible
> and therefore considered for overload resolution,
> even if the function instantiation has not yet been
> defined.

The declaration itself can involve the template
parameters in such a way that it is legal for some
types and illegal for others. I see no reason to
change the behavior away from what exists currently.
If you must instantiate a template for a type for
which the friends will be incorrect, write a
specialization which doesn't contain them.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Garry Lancaster" <glancaster@ntlworld.com>
Date: Wed, 8 May 2002 19:53:57 GMT
Raw View
> Garry Lancaster wrote:
> > Actually it won't use either. The default comparison
> > operator for std::set is <, not ==.

Hyman Rosen:
> I'm calling std::find on a sequence.
> 25.1.2 says that operator== is used.

Oops. Right you are. Of course, normally you wouldn't
use that overload of std::find on a set because
std::set::find is more efficient. But still, I should
have read what you actually wrote ;-)

> > We need to make the distinction between declaration
> > and definition. It is right that the declaration is visible
> > and therefore considered for overload resolution,
> > even if the function instantiation has not yet been
> > defined.
>
> The declaration itself can involve the template
> parameters in such a way that it is legal for some
> types and illegal for others.

Could you explain what you mean by that? Are you
referring to the fact that pass-by-value and return-
by-value require a copy ctor? If so, isn't that a
separate issue, and one that affects all functions?

> I see no reason to
> change the behavior away from what exists currently.

A couple of reasons:

1. It means that the body of a function template that
is never called is instantiated. I could be wrong
but I think this is the only way this can happen
unless using explicit instantiation. As I already mentioned,
that is inconsistent with member function behaviour,
but it's also inconsistent with the behaviour of
non-friend non-member functions.

2. According to the write-up in issue 329, many
compilers do it this way anyway so we'd be standardising
existing practice.

> If you must instantiate a template for a type for
> which the friends will be incorrect, write a
> specialization which doesn't contain them.

Of course. But by having to write your own
specialization you lose out on the re-use
advantage of the generic class template.

Perhaps you could give some reasons why the behaviour
should remain as currently specified? Previously you
wrote:

"the point of the existing mechanism is to
force these functions into the enclosing namespace so
that they are available for overload resolution."

But as I pointed out, that just requires the
declaration to be visible, not the definition.
In either case, your example code would have the
behaviour you wish it to have.

Kind regards

Garry Lancaster
Codemill Ltd
Visit our web site at http://www.codemill.net

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]