Topic: (proposal) conversions for template functions (long)
Author: "Brian Parker" <brianjparker@ozemail.com.au>
Date: Sun, 11 Nov 2001 17:06:27 GMT Raw View
Hi,
The following are some ideas I have had to improve the support for proxy
classes in C++. The first proposal- to allow limited conversions when
calling template functions is, however, very widely applicable and
essentially just fixes a flaw in the current C++ definition...
Introduction:
Proxy classes are an important design technique widely used in C++. Proxy
objects are temporary objects returned from an operator to distinguish
rvalue and lvalue usage; they rely upon the conversion operator being called
in any rvalue usage to convert to the final user-visible type. Such proxy
classes are used in std::valarray, std::vector<bool> and are widely used in
expression template libraries.
They use of a proxy class in an implementation is intended to be transparent
to the user, but unfortunately, there are several limitations in the current
definition of C++98 that cause classes implemented using proxy techniques to
behave differently to other classes. It is the intent of this paper to
highlight these situations and propose solutions.
Problem 1: no conversions performed for template functions
When calling a non-template function, if the actual argument type does not
match the formal parameter type then type conversions are done, if possible,
to convert to the formal parameter type. This is, of course, essential to
the operation of a proxy class.
When calling a template function with a fully generic template parameter,
quite correctly, no conversions are done as they are unnecessary as a type
can always be deduced in this case.
But when calling a template function where the type of a template parameter
is partially defined, in particular when the function parameter is of the
form C<T> and type deduction fails then C++98 attempts no type conversion
and the build fails. What should be done in the case is that partial type
conversion should be attempted- if the function argument can be
unambiguously converted to a type of the form C<T> then that conversion
should be done. Note that this is already required in C++98 for the special
case of conversion from a derived type to a base type of the form C<T>. The
standard should be generalised to consider arbitrary conversions.
Case study:
Although implicit conversions are useful in a number of cases, this
limitation particularly affects proxy classes.
e.g.
A generic function that works for a valarray of any type is written as
follows-
template<typename T>
void func(const std::valarray<T>& V)
{
.
}
But this function unexpectedly fails to compile in the following call as the
proxy object (slice_array) does not have its conversion operator called to
convert to a valarray<double>.
std::valarray<double> V(100);
func(V[std::slice(1, 4, 1)]); // compile error
The caller needs, surprisingly, to do an explicit cast
func(static_cast<std::valarray<double> >(V[std::slice(1, 4, 1)])); // OK
This is a particular problem for proxy classes as they are designed to be
transparent to the user and so the reason for the compile-time error may not
be obvious.
(Note that a fully generic function-
template<typename T>
void generic_func(const T& t)
{
// uses a T assuming it has a valarray interface
.
}
will still fail to call the conversion operator of the proxy class, but such
completely generic functions will need to be aware of this and handle it
explicitly. More complete proxy class support would be needed to handle this
case (see below) )
Proposed Solution:
The solution is simply to require that for a template parameter of the form
C<T>, if type deduction fails then if an (unambiguous) conversion exists to
a C<T> then the conversion is performed.
Note that this change is fully backward compatible as it applies only in
cases that would otherwise not compile.
Note that if it was deemed necessary to limit this conversion then it could
be limited in several ways (I don't think, however, that these limitations
are necessary. I present them only for completeness.)-
(1) The conversion lookup could be limited only to conversion operators of
the argument type.
(2) The conversion lookup could be limited only to conversion operators of
the argument type which are marked with some special keyword (e.g. "proxy"
or maybe "template" could be overloaded for this purpose (see below) ).
Proposed change to wording of standard:
14.8.2.1 [temp.deduct.call]
Add the following clause at the obvious place-
"-- If P is a class, and P has the form template-id, then A can be a type
unambiguously convertible to the deduced A. "
Problem 2:
The above proposal would go a long way to making proxy classes transparent
to the user, but there are remaining issues that would still cause
proxy-implemented classes to behave differently-
(1) the proxy conversion "uses up" the single allowed user-defined
conversion.
(2) Fully generic functions (like generic_func above) would still handle
proxy classes incorrectly.
If an additional keyword e.g. "proxy" was added as a qualifier on
conversion operators, then even better support for proxy objects could be
supported by making such a proxy conversion operator "free" so that is doesn
't count toward the limit of one user-defined conversion and, most
importantly, by causing it to be called in any rvalue useage before template
type deduction. This would allow the fully generic function generic_func()
above to work directly with proxy classes like slice_array.
(a template function with a parameter fully or partially matching the proxy
class would, however, not perform the conversion; this would be needed to
implement expression template libraries).
Note that this is an orthogonal issue and should be considered independently
of the template conversion proposal above.
Conclusion:
The calling of user-defined conversions for partially defined template
parameters fills an obvious hole in the current C++ rules and gives the
behaviour that one would expect- the current C++98 rules are surprising. It
is especially important for the support of proxy classes, however it would
be generally useful in easing the use of any class with user-defined
conversions with template functions. Such other examples arise frequently,
e.g. a class that has an iterator class that is convertible to a
const_iterator fails when calling a generic function taking two
const_iterators.
Completely transparent useage of proxy classes, however, would only be
possible if explicit support for them was included (e.g. by the "proxy"
keyword suggested above). This second proposal is more speculative and
requires more extensive core language changes.
I would be interested in any comments on these proposals, and in particular
cases where they would still allow the use of proxy objects to be visible to
the user, or potential implementation difficulties.
,Brian Parker
(brianjparker@ozemail.com.au)
---
[ 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.research.att.com/~austern/csc/faq.html ]
Author: "Anthony Williams" <anthwil@nortelnetworks.com>
Date: Mon, 12 Nov 2001 11:20:42 GMT Raw View
"Brian Parker" <brianjparker@ozemail.com.au> wrote in message
news:3ZlH7.2863$c87.79842@ozemail.com.au...
> Hi,
>
> The following are some ideas I have had to improve the support for proxy
> classes in C++. The first proposal- to allow limited conversions when
> calling template functions is, however, very widely applicable and
> essentially just fixes a flaw in the current C++ definition...
>
> Introduction:
> Proxy classes are an important design technique widely used in C++. Proxy
> objects are temporary objects returned from an operator to distinguish
> rvalue and lvalue usage; they rely upon the conversion operator being
called
> in any rvalue usage to convert to the final user-visible type. Such proxy
> classes are used in std::valarray, std::vector<bool> and are widely used
in
> expression template libraries.
>
> They use of a proxy class in an implementation is intended to be
transparent
> to the user, but unfortunately, there are several limitations in the
current
> definition of C++98 that cause classes implemented using proxy techniques
to
> behave differently to other classes. It is the intent of this paper to
> highlight these situations and propose solutions.
>
> Problem 1: no conversions performed for template functions
> When calling a non-template function, if the actual argument type does not
> match the formal parameter type then type conversions are done, if
possible,
> to convert to the formal parameter type. This is, of course, essential to
> the operation of a proxy class.
>
> When calling a template function with a fully generic template parameter,
> quite correctly, no conversions are done as they are unnecessary as a type
> can always be deduced in this case.
>
> But when calling a template function where the type of a template
parameter
> is partially defined, in particular when the function parameter is of the
> form C<T> and type deduction fails then C++98 attempts no type conversion
> and the build fails. What should be done in the case is that partial type
> conversion should be attempted- if the function argument can be
> unambiguously converted to a type of the form C<T> then that conversion
> should be done. Note that this is already required in C++98 for the
special
> case of conversion from a derived type to a base type of the form C<T>.
The
> standard should be generalised to consider arbitrary conversions.
[snipped example]
> Proposed Solution:
> The solution is simply to require that for a template parameter of the
form
> C<T>, if type deduction fails then if an (unambiguous) conversion exists
to
> a C<T> then the conversion is performed.
>
> Note that this change is fully backward compatible as it applies only in
> cases that would otherwise not compile.
But it may introduce ambiguities where there previously were none.
> Note that if it was deemed necessary to limit this conversion then it
could
> be limited in several ways (I don't think, however, that these limitations
> are necessary. I present them only for completeness.)-
> (1) The conversion lookup could be limited only to conversion operators of
> the argument type.
> (2) The conversion lookup could be limited only to conversion operators of
> the argument type which are marked with some special keyword (e.g. "proxy"
> or maybe "template" could be overloaded for this purpose (see below) ).
This could be used to limit it to new code, to maintain
backwards-compatibility.
[snipped proposed wording]
> Problem 2:
> The above proposal would go a long way to making proxy classes transparent
> to the user, but there are remaining issues that would still cause
> proxy-implemented classes to behave differently-
> (1) the proxy conversion "uses up" the single allowed user-defined
> conversion.
> (2) Fully generic functions (like generic_func above) would still handle
> proxy classes incorrectly.
>
> If an additional keyword e.g. "proxy" was added as a qualifier on
> conversion operators, then even better support for proxy objects could be
> supported by making such a proxy conversion operator "free" so that is
doesn
> 't count toward the limit of one user-defined conversion and, most
> importantly, by causing it to be called in any rvalue useage before
template
> type deduction. This would allow the fully generic function
generic_func()
> above to work directly with proxy classes like slice_array.
> (a template function with a parameter fully or partially matching the
proxy
> class would, however, not perform the conversion; this would be needed to
> implement expression template libraries).
This is, IMO, a better solution than the first. You could even make proxies
act entirely transparently --- if an lvalue is of type T, it could refer to
a real object of type T, or an object of a proxy class for type T. This is
quite a major change, though.
> Note that this is an orthogonal issue and should be considered
independently
> of the template conversion proposal above.
>
> Conclusion:
> The calling of user-defined conversions for partially defined template
> parameters fills an obvious hole in the current C++ rules and gives the
> behaviour that one would expect- the current C++98 rules are surprising.
It
> is especially important for the support of proxy classes, however it would
> be generally useful in easing the use of any class with user-defined
> conversions with template functions. Such other examples arise frequently,
> e.g. a class that has an iterator class that is convertible to a
> const_iterator fails when calling a generic function taking two
> const_iterators.
>
> Completely transparent useage of proxy classes, however, would only be
> possible if explicit support for them was included (e.g. by the "proxy"
> keyword suggested above). This second proposal is more speculative and
> requires more extensive core language changes.
>
> I would be interested in any comments on these proposals, and in
particular
> cases where they would still allow the use of proxy objects to be visible
to
> the user, or potential implementation difficulties.
Anthony
--
Anthony Williams
Software Engineer, Nortel Networks Optical Components Ltd
The opinions expressed in this message are not necessarily those of my
employer
---
[ 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.research.att.com/~austern/csc/faq.html ]
Author: "Brian Parker" <brianjparker@ozemail.com.au>
Date: Tue, 13 Nov 2001 01:45:12 GMT Raw View
"Anthony Williams" <anthwil@nortelnetworks.com> wrote in message
news:9so4eo$13ttu1$1@ID-49767.news.dfncis.de...
> "Brian Parker" <brianjparker@ozemail.com.au> wrote in message
> news:3ZlH7.2863$c87.79842@ozemail.com.au...
....
> >
> > Note that this change is fully backward compatible as it applies only in
> > cases that would otherwise not compile.
>
> But it may introduce ambiguities where there previously were none.
That is probably true. Can you provide an example?
Note that if there are overloaded template functions then partial ordering
will prefer any template function that doesn't rely upon this proposed
conversion.
And if there is an overloaded non-template function then presumably it will
rely upon the same conversion to proxy class and so would be prefered.
I suspect that any such example where backward compatibility is broken would
be *extremely* contrived, as the proposal only allows the intuitively
obvious overloaded template function to be called, and any code that relid
upon the subtleties of overload resolution to avoid calling this template
function would almost certainly be a bug, or at least very fragile.
> > Note that if it was deemed necessary to limit this conversion then it
> could
> > be limited in several ways (I don't think, however, that these
limitations
> > are necessary. I present them only for completeness.)-
> > (1) The conversion lookup could be limited only to conversion operators
of
> > the argument type.
> > (2) The conversion lookup could be limited only to conversion operators
of
> > the argument type which are marked with some special keyword (e.g.
"proxy"
> > or maybe "template" could be overloaded for this purpose (see below) ).
>
> This could be used to limit it to new code, to maintain
> backwards-compatibility.
Yes, (actually, I submitted a variant of this proposal as a public comment
in the original standards process, which would, of course, have avoided
backward-compatibility issues).
If a new keyword was introduced just to mark conversions allowed in template
function calls, then I think I would prefer "template" (though that may be
syntactic issues that prevent that- I haven't analysed that fully).
>
> [snipped proposed wording]
>
> > Problem 2:
> > The above proposal would go a long way to making proxy classes
transparent
> > to the user, but there are remaining issues that would still cause
> > proxy-implemented classes to behave differently-
> > (1) the proxy conversion "uses up" the single allowed user-defined
> > conversion.
> > (2) Fully generic functions (like generic_func above) would still handle
> > proxy classes incorrectly.
> >
> > If an additional keyword e.g. "proxy" was added as a qualifier on
> > conversion operators, then even better support for proxy objects could
be
> > supported by making such a proxy conversion operator "free" so that is
> doesn
> > 't count toward the limit of one user-defined conversion and, most
> > importantly, by causing it to be called in any rvalue useage before
> template
> > type deduction. This would allow the fully generic function
> generic_func()
> > above to work directly with proxy classes like slice_array.
> > (a template function with a parameter fully or partially matching the
> proxy
> > class would, however, not perform the conversion; this would be needed
to
> > implement expression template libraries).
>
> This is, IMO, a better solution than the first. You could even make
proxies
> act entirely transparently --- if an lvalue is of type T, it could refer
to
> a real object of type T, or an object of a proxy class for type T. This is
> quite a major change, though.
Yes, though note that the template conversion proposal is still very useful
beyond proxy classes- I have several examples where I have been forced to
introduce an inheritance hierarchy between classes to allow their intuitive
use with template functions where a conversion operator would have been more
appropriate.
As far as full proxy class support goes, another possibility which I didn't
mention that I think may be preferable is to allow overloaded versions of
operators which are called in lvalue and rvalue situations (this is
discussed in Design and Evolution of C++ (3.7.1)).
,Brian Parker
---
[ 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.research.att.com/~austern/csc/faq.html ]