Topic: void as type and templates


Author: usenet@kth.se (Usenet)
Date: 4 May 93 15:13:30 GMT
Raw View
There is a problem with the combination of the status of void and
templates which I would like to take up to discussion. It arose when I
wanted to be able to extract typed data from an input stream and
during that extraction to call a function on the data already
extracted from the stream. All this should be done in a type safe
manner of cours. What it boiled down to for me was the following.

template<class R> Extractor {
public:
    virtual R operator()(istream&) = 0;
};

template<class R,class A1,class A2>
class FunctionExtractor : public Extractor<R> {
public:
    FunctionExtractor(R (*f)(A1,A2)) : func(f) { };
    virtual R operator()(istream&);
private:
    R (*func)(A1,A2);
};

template<class R,class A1,class A2>
R FunctionExtractor<R,A1,A2>::operator()(istream& istr)
{
    A1 a1;
    istr >> a1;
    A2 a2;
    istr >> a2;
    return fun(a1,a2);
}

(Except that I want this for any number of arguements to a function but
that's neither here nor there.)

OK, but what happens with functions that don't return anything? This
is something I want because the precise situation is that I want to
extract an int, say, from the stream. I look at the first byte of the
stream which tells me the type of the next object. If that type is INT
then there is an int waiting for me next. If it is FUNCTION then there
is a string waiting for me which I extract, use it to look up a
pointer to Extractor<int> which is a coerced FunctionExtractor<int,..>
pointer in a table and then use it to extract an int. Finally, if it
is VOID then there is again a string waiting for me which I extract
and use it to look up a pointer to an Extractor<void> which is really
a pointer to FunctionExtractor<void,..> and then use it to call a
function leaving no result and *then* start afresh attempting to
extract an int. Thus we would have something like

template<class R> R ExtractClass<R>::Extract(istream& istr)
{
    TokenCode code;
    istr >> code;
    if (code == CodeOfType((R *)0))
    {
        R r;
        istr >> r;
        return r;
        }
    else if (code == FUNCTION)
    {
        Function fun;
        istr >> fun;
        Extractor<R> *extr = Lookup<R>::Lookup(fun);
        return (*extr)(istr);
       }
    else if (code == VOID)
    {
        Function fun;
        istr >> fun;
        Extractor<void> *extr = LookUp<void>::Lookup(fun);
        (*extr)(istr);
        return ExtractClass<R>::Extract(istr);
       }
    else
        return ExtractClass<R>::TokenTypeError(code);
    }

Now, the problem is that this doesn't work because of the

return fun(a1,a2);

line above when R is instantiated to void. Also it isn't clear to me
if it is allowed to instantiate a template class argument to void.
Hence my questions:

1) Is there any reason no to allow allow a return statement of the
form
return f(...)
where f is a function of type
void f(...)
and the return statement is done in a function also returning void? As
far as I can see there would be no backwards compatability problems
and if it is not allowed then one would be forced to duplicate
template code (which was what I did).

2) Is void enough of a type that it is allowed to use it as a template
class argument in an instantiation of a template?
--

                            Torsten Ekedahl
                            teke@matematik.su.se




Author: g2devi@cdf.toronto.edu (Deviasse Robert N.)
Date: Tue, 4 May 1993 17:41:15 GMT
Raw View
In <1993May4.151330.18861@kth.se> usenet@kth.se (Usenet) writes:

>There is a problem with the combination of the status of void and
>templates which I would like to take up to discussion. It arose when I
>wanted to be able to extract typed data from an input stream and
>during that extraction to call a function on the data already
>extracted from the stream. All this should be done in a type safe
>manner of cours. What it boiled down to for me was the following.

>template<class R> Extractor {
>public:
>    virtual R operator()(istream&) = 0;
>};

>template<class R,class A1,class A2>
>class FunctionExtractor : public Extractor<R> {
>public:
>    FunctionExtractor(R (*f)(A1,A2)) : func(f) { };
>    virtual R operator()(istream&);
>private:
>    R (*func)(A1,A2);
>};

>template<class R,class A1,class A2>
>R FunctionExtractor<R,A1,A2>::operator()(istream& istr)
>{
>    A1 a1;
>    istr >> a1;
>    A2 a2;
>    istr >> a2;
>    return fun(a1,a2);
>}

>(Except that I want this for any number of arguements to a function but
>that's neither here nor there.)

>OK, but what happens with functions that don't return anything? This
>is something I want because the precise situation is that I want to
>extract an int, say, from the stream. I look at the first byte of the
>stream which tells me the type of the next object. If that type is INT
>then there is an int waiting for me next. If it is FUNCTION then there
>is a string waiting for me which I extract, use it to look up a
>pointer to Extractor<int> which is a coerced FunctionExtractor<int,..>
>pointer in a table and then use it to extract an int. Finally, if it
>is VOID then there is again a string waiting for me which I extract
>and use it to look up a pointer to an Extractor<void> which is really
>a pointer to FunctionExtractor<void,..> and then use it to call a
>function leaving no result and *then* start afresh attempting to
>extract an int. Thus we would have something like

>template<class R> R ExtractClass<R>::Extract(istream& istr)
>{
>    TokenCode code;
>    istr >> code;
>    if (code == CodeOfType((R *)0))
>    {
>        R r;
>        istr >> r;
>        return r;
>        }
>    else if (code == FUNCTION)
>    {
>        Function fun;
>        istr >> fun;
>        Extractor<R> *extr = Lookup<R>::Lookup(fun);
>        return (*extr)(istr);
>       }
>    else if (code == VOID)
>    {
>        Function fun;
>        istr >> fun;
>        Extractor<void> *extr = LookUp<void>::Lookup(fun);
>        (*extr)(istr);
>        return ExtractClass<R>::Extract(istr);
>       }
>    else
>        return ExtractClass<R>::TokenTypeError(code);
>    }

>Now, the problem is that this doesn't work because of the

>return fun(a1,a2);

>line above when R is instantiated to void. Also it isn't clear to me
>if it is allowed to instantiate a template class argument to void.
>Hence my questions:

>1) Is there any reason no to allow allow a return statement of the
>form
>return f(...)
>where f is a function of type
>void f(...)
>and the return statement is done in a function also returning void? As
>far as I can see there would be no backwards compatability problems
>and if it is not allowed then one would be forced to duplicate
>template code (which was what I did).

>2) Is void enough of a type that it is allowed to use it as a template
>class argument in an instantiation of a template?
>--

>                            Torsten Ekedahl
>                            teke@matematik.su.se


Why not try defining the specialized version of your class? Here's an example:

 template<class X>
   class Foo{
      public:
        X bar(X x) {return x;}
   };

 // Specialization of the general Foo<X> for X==void
 class Foo<void>{
   public:
     void bar()      {}
 };

 void main(){
   Foo<int> fint;
   Foo<void> fvoid;

   cout << fint.bar(1969);
   fvoid.bar();
 }

The Foo<void> type is different from the F<X> type in a few significant
ways and so we have to write another specialized version for it. I know
it's a pain to respecify a class that looks almost identical, but I suppose
common code can be factored into a base class.


Take care
    Robert
--
/----------------------------------+------------------------------------------\
| Robert N. Deviasse               |"If we have to re-invent the wheel,       |
| EMAIL: g2devi@cdf.utoronto.ca    |  can we at least make it round this time"|
+----------------------------------+------------------------------------------/




Author: jamshid@emx.cc.utexas.edu (Jamshid Afshar)
Date: 5 May 1993 01:07:45 -0500
Raw View
Btw, Torsten, tell your sysadmin that your "From:" line address and
name are messed up ("usenet@kth.se (Usenet)") and that "comp" is not a
valid "Distribution:".

In article <1993May4.151330.18861@kth.se> teke@matematik.su.se (Torsten Ekedahl) writes:
[...deleted...]
Torsten asks whether the class template:

 template<class R> class Extractor {
 public:
     R operator()(istream&) {
        R r;
        /*...*/
        return r;
     }
 };

can be instantiated with an 'R' of 'void'.

 Extractor<void> e;  // legal
 e(cin);

I think there are three separate issues.  The first is whether it's
legal to instantiate a template with 'void'.  The ARM grammar allows
'void' to be a template argument, and I can't find any special
restrictions in Ch.14, so:
 template<class T> class X {
 public:
    T foo();
 };
 X<void> x;
 x.foo();
appears to be legal.  I believe this leads to the following also being
legal.
 template<class T> T foo(T*);
 void* p;
 foo(p);
I'm not sure that the body of either 'foo()' can be defined, though.

The second issue is whether you can 'return' a 'void' expression as in:
 void f();
 void g() { return f(); }  // error
This is not currently allowed (ARM 6.6.3).  I don't think changing
this would have significant ramifications, but it's definitely not
worth changing unless the third issue is resolved.

The third issue is whether objects of type 'void' can be allowed:
 void r;
 struct S { void r; };
 void f(void r);
 void g() { return void(); };
I'm not sure 'void' objects could be allowed without ugliness and a
bunch of extra wording and work in ANSI C++.  Among the issues that
would have to be settled are sizeof(void), 'new void[10]',
assignment/initialization, and 'void' function parameter type (since
it currently means "no parameters").  The effect these changes would
have on the semantics of 'void*'s would also have to be considered.

I haven't investigated this matter thoroughly but I do believe that
rules could be devised, without breaking any existing C or C++ code,
which allow 'void' to be used more like a "real" type.  While these
changes would probably not be uglier nor more complex than many other
things in C++, I personally don't feel they would be worth the trouble
of introducing them nor the added complexity to the language.  The
fact that it would be such a dramatic change from ANSI C's definition
of 'void' is what bothers me most.

I encountered the problem of 'void' return types with my Functor
classes (see my recent c.l.c++ article in one of the "callbacks"
threads).  I just ended up writing two separate versions of the
classes: one with a specified return type "R", and one with a return
type of 'void'.

 template<class R, class P> class Functor {
 public:
    R operator()(P);
 };
 template<class P> class Functorv {
 public:
    void operator()(P);
 };

Since I need separate versions of the classes for callbacks with
different numbers of parameters, and would need separate versions for
callbacks to 'const' member functions, I don't think the current state
of affairs wrt to 'void' imposes a serious burden.

The idea of using specializations to solve the problem, as presented
in another recent followup, is very interesting.  Unfortunately
specializations aren't useful when there are other template arguments:

 template<class R, class P> class Functor {...};
 template<class P> class Functor<void,P> {...};  // error:
 // specializations can't be templates

Jamshid Afshar
jamshid@emx.utexas.edu