From 4669448216867988513
X-Google-Language: ENGLISH,ASCII-7-bit
X-Google-Thread: f78e5,bb2cedb5166b3859,start
X-Google-Attributes: gidf78e5,public
X-Google-ArrivalTime: 2001-11-11 09:07:27 PST
Path: archiver1.google.com!news1.google.com!newsfeed.stanford.edu!newsfeeds.belnet.be!news.belnet.be!colt.net!dispose.news.demon.net!news.demon.co.uk!demon!mail2news.demon.co.uk!not-for-mail
From: "Brian Parker" <brianjparker@ozemail.com.au>
Newsgroups: comp.std.c++
Subject: Typeof considered harmful (long)
Date: Sun, 11 Nov 2001 17:06:18 GMT
Organization: OzEmail Ltd, Australia
Approved: Fergus Henderson <fjh@cs.mu.oz.au>, moderator of comp.std.c++
Message-ID: <fTlH7.2855$c87.79835@ozemail.com.au>
X-Trace: mail2news.demon.co.uk 1005498385 mail2news:20379 mail2news mail2news.demon.co.uk
X-Complaints-To: abuse@demon.net
X-Mail2News-Path: news.demon.net!mulga.cs.mu.oz.au
X-Authentication-Warning: mulga.cs.mu.OZ.AU: fjh set sender to devnull@stump.algebra.com using -f
X-Robomod: STUMP, ichudov@algebra.com (Igor Chudov)
X-Priority: 3
X-MSMail-Priority: Normal
X-Newsreader: Microsoft Outlook Express 5.50.4807.1700
X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4807.1700
NNTP-Posting-Date: Sun, 11 Nov 2001 14:02:35 EST
Lines: 330
Xref: archiver1.google.com comp.std.c++:8124

Hi all,

The following is a proposal I have been considering that gives the benefits
of typeof with only minor extensions to the current C++ type deduction
rules...
Introduction:

The type deduction mechanism of C++98 has several limitations that
unnecessarily limit aspects of generic programming. As a solution for these
limitations, it has often been proposed that a typeof() operator be added as
a language extension. In this paper, it is shown that typeof(), whilst
solving most such problems, is syntactically clumsy and has its own
limitations. As an alternative, the semantics of type deduction in C++ can
be extended without adding a new keyword to produce a more powerful, more
elegant, and easier to use solution. If  this extension was implemented, the
language would have much more complete support for generic programming.

Problem Statement:
The fundamental problem is that in a template function such as the
following:

template<typename R, typename P1, typename P2, typename L>
R func(P1 p1, P2 p2)
{
 L  local  = p1 * p2;

 return local * p2;
}

When called in the typical fashion (without explicit template specification)

MyType1 p1;
MyType2 p2;
MyType3 p3;

p3  =  func(p1, p2);

ISO C++ will do type deduction to deduce the type of arguments P1 and P2,
but will not perform type inference of type L or type R. Such unknown local
variable and return types occur in generic functions which can be
instantiated with any P1 and P2.

The typeof solution:
It has been proposed to add a typeof() operator to the language. This
operator would, similarly to sizeof, not evaluate its contained expression
but would deduce the expression's type at compile time. With typeof(), the
above function can have all of its template parameters deduced by adding
typeofs in the appropriate places-

template<typename P1, typename P2 >
typeof((P1( ) * P2( ) )* P2( )) func(P1 p1, P2 p2)
{
 typeof(p1 * p2)  local  = p1 * p2;

 return local * p2;
}

Although this solves the main problem, there are several problems with
typeof-

(1) For local variables it is unnecessarily verbose- it redundantly repeats
the initializing expression, leading to the possibility of obscure errors
(which would be difficult to track down as the final compile-time types
would not be visible).

(2) For the return type, unlike the case for local variables, the variables
of the return expression cannot be referred to and so the typeof operator
would need obscure dummy arguments of the same type added (see example above
where default constructed temporaries are used; in fact this has a problem
where the unknown type is not default constructible, requiring kludges like
casting a null pointer to the required type).

(3)  In the case where several function return paths return different,
convertible, types it is not clear which expression should be used in the
return typeof.

(4) For the return type, although the majority of cases in which typeof is
needed lead to small expressions which may be manageable, in principle one
can construct cases where essentially the whole of the function body would
be need to be listed in the return typeof . As well as being very difficult
to read, how to handle multi-statement sequences is unclear.

(5) The final result is syntactically ugly and difficult to write and read;
it essentially adds code, in the form of the return expression, to the
function prototype.

Proposed solution by extending C++  type deduction:
The fundamental problem is that L and R in the above template function are
essentially non-deduced contexts. A solution, then is to specify that after
function argument deduction, if function template parameters remain
non-deduced and are not used as part of the function signature (excluding
the return type), then the type is deduced to be that of the initializing
expression (or more accurately, it is deduced by the same algorithm that a
function parameter is deduced to match an initialising function argument).

So, for example, L above will be deduced to be the type of the initializing
expression "p1 * p2", and R will be deduced as the type of the return
expression "local * p2".

A difficulty with the return type deduction is that there can be multiple
return paths from a function. If this is the case then the type R is deduced
as the widest type of all return paths (this is the same rule used for the
return type of the conditional operator (?:) ). If there is no single
convertible type then, of course, the deduction fails.

At first glance, it may appear that a problem with this solution is that the
function source code needs to be available to the compiler to deduce the
function prototype when the template is instantiated. But this is, of
course, a requirement for a template function anyway, and, as discussed
above, using typeof for the return type essentially requires the user to put
a large part (potentially all) of a function's implementation in the
function prototype in type form anyway.

Note: it may also be argued that a minor disadvantage of this solution is in
the usage of typeof in a non-template function such as the following

void func()
{
 .
 typeof(compose(f1, f2)) ff = compose(f1, f2);
 .
}

(Such a usage, although strictly unnecessary as all types are in principal
known, may still be convenient if the type if large and unwieldy as a return
type from a compose operator often is)

The proposed extended type-deduction solution would require func( ) to
become a template function, with the attendant compile-time costs of
template functions

template<typename L>
void func()
{
 .
 L ff = compose(f1, f2);
 .
}

However, a good implementation of export could detect this case and
essentially treat it as a non-template function (i.e. create a fully
compiled instantiation).

In summary, the advantages of this extension to the C++ type deduction
mechanism include-
(1) Fully backward compatible- it simply allows code to compile that would
previously fail to compile.
(2) No new keyword needed.
(3) Adds minimal verbiage compared to the use of typeof, and has a
straightforward interpretation that cannot be accidentally used incorrectly.

Case Studies:

(I) Maximum function

The first case study is implementing a max( )  function that can take any
convertible arguments e.g. int and double

The standard library version of max( )
template<typename T>
T std::max(T t1, T t2 )
{
 if (t1 > t2)
  return t1;
 else
  return t2;
}

fails for different convertible types as t1 and t2 must be exactly the same
type.

A first attempt using typeof:

template<typename T1, typename T2>
typeof(???) std::max(T1 t1, T2 t2 )
{
 if (t1 > t2)
  return t1;
 else
  return t2;
}

but it is not clear what the return type should be.

Using a conditional expression converts to a common type:

template<typename T1, typename T2>
typeof((T1() > T2()) ? T1() : T2();) std::max(T1 t1, T2 t2 )
{
 return (t1 > t2) ? t1 : t2;
}

The solution using extended type inference is straightforward and obvious:

template<, typename R, typename T1, typename T2 >
R std::max(T1 t1, T2 t2 )
{
 if (t1 > t2)
  return t1;
 else
  return t2;
}

(Note: for this example it is, in fact, possible to write a traits class
convert_type<T1, T2>::convert_t, that allows the lookup of the common
conversion type of two types, so it could be used in this example instead of
a full typeof implementation, but it is still fundamentally a kludge and
more verbose than the second example above.)


(II) Function composition

A function object composition operator looks something like the following-

template<typename F1,  typename F2>
compose_func_object<F1, F2> compose(F1 f1, F2 f2)
{
 return compose_func_object<F1, F2>(f1, f2);
}

where compose_func_object is a new function object that stores the original
function objects and computes their composition.

Implementing compose_func_object requires deducing the function objects's
return type when it is called.

The standard library solution is to require function objects to redundantly
include member typedefs describing the return type

template<typename F1,  typename F2>
struct compose_func_obj
{
 typedef typename F1::return_type return_type;
 ..

 template<typename P>
 typename F1::return_type operator()(P  p)
{
 return m_f1(m_f2(p));
}
..
};

With a typeof operator, the redundant type information is not needed, but
still the clumsy redundant syntax is required which could harbour *very*
obscure bugs if the type expression is incorrect.

template<typename F1,  typename F2>
struct compose_func_obj
{

template< typename P>
 typeof( F2()(F1()) ) operator()(P p)
{
 return m_f1(m_f2(p));
}
..
};

// oops.. that should have been "typeof( F1()(F2()) )"
template<typename F1,  typename F2>
struct compose_func_obj
{
template< typename P>
 typeof( F1()(F2()) ) operator()(P p)
{
 return m_f1(m_f2(p));
}
..
};

The solution using extended type inference is direct, with no possibility of
an incorrect return type being specified.

template<typename F1,  typename F2>
struct compose_func_obj
{
template<typename R, typename P>
 R operator()(P p)
{
 return m_f1(m_f2(p));
}
..
};

Proposed change to text of standard:

14.8 [temp.deduct]

Add the following text-

"
Deducing local variables and function return type

If a template parameter P is not deduced from a function call (14.8.2.1) and
is not explicitly specified (14.8.1) then it is deduced as per (14.8.2.1)
where the argument type A is taken to be the type of the initializing
expression. If there is no initializing expression, then it is a non-deduced
context. For a function return template parameter, the argument type A is
taken to be the widest type of the return expression(s) of the function
where the widest type is deduced as per the conditional expression 5.16
[exp.cond].
"

(this wording could obviously be improved, and the text from the conditional
operator conversion to widest type algorithm could be inserted inline).


Conclusion:
As far as I am aware, this proposed extension to the type deduction
mechanism of C++ provides all of the power of typeof in a much more
straightforward and cleaner manner. I would be very interested to hear of
any examples where typeof is needed that cannot be handled by this extended
type inference proposal.
Also, I would like to hear of any comments on any fundamental implementation
difficulties or other problems with the suggested scheme.

,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                ]



