Topic: Typesafe variadic functions


Author: Barney Pitt <barney@russell.ee.umist.ac.uk>
Date: 1998/12/04
Raw View
Andrei Alexandrescu wrote:
>
> Very thorough proposal.
> However, it allows only for functions with the same type for all variable
> arguments. But there is a simple and elegant solution for this. Please see
> my article in C++ Users Journal on September 1998, "Inline Containers for
> Variable Arguments". We've used this is a medium-sized project with
> excellent results.

If there's a copy of that article online, I'd appreciate
a pointer to it.

Thanks

Barney Pitt,
UMIST


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1998/12/04
Raw View
Barney Pitt wrote in message <3667F8A1.65DA@russell.ee.umist.ac.uk>...
>
>Andrei Alexandrescu wrote:
>>
>> Very thorough proposal.
>> However, it allows only for functions with the same type for all variable
>> arguments. But there is a simple and elegant solution for this. Please
see
>> my article in C++ Users Journal on September 1998, "Inline Containers for
>> Variable Arguments". We've used this is a medium-sized project with
>> excellent results.
>
>If there's a copy of that article online, I'd appreciate
>a pointer to it.


Barney,

Sorry but the agreement is so strict, that I would get arrested if I
published the text in here :o).
Besides, it's not rocket science: basically you overload operator() so you
are able to write:

void f(const std::vector<int> & Data);
void g(const std::list<double> & Data);
void g(const std::deque<long> & Data);
//...

f(make_vector(1)(3)(5)(101));
f(make_list(1.5)(4.53)(2.5)(8.54));
f(make_deque(5L)(3)(5)(101));

(and of course this works for non-primitive types too). The point of the
article was an efficient and "dense" implementation that used the same
codebase for all make_* functions.

Andrei




[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1998/11/28
Raw View
Francis Glassborow wrote:
>
> You have five years to gain experience, lobby implementors to try it
> as an extension etc.  For the time being WG21 is tasked with
> maintaning the current standard and producing whatever technical
> reports SC22 deem appropriate.  WG21 will not even consider extensions
> to the C++ standard until 2003 at the earliest with the hope of
> delivery in 2008.

Are you serious? We have to wait ten years before anything new might be
added to the language? I don't really know what's physically involved
with "maintaining the current standard and producing whatever technical
reports SC22 deems appropriate," but it has to be a heck of a lot easier
than what's been going on over the past five years. Yet you seem to be
suggesting that the standards cycle consists of five years of developing
a standard, then five years of maintaining it and tweaking it, then five
years of developing the next version of the standard, with no overlap.
That doesn't make sense to me.

--

Ciao,
Paul
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]





Author: "Paul D. DeRocco" <pderocco@ix.netcom.com>
Date: 1998/11/28
Raw View
Siemel Naran wrote:
>
> In your propasal below, each element in the variable list has the
> same type.  Homogenous lists like these are the most common I
> think.  They arise whenever we want to create (usually short)
> arrays on the fly, as you've pointed out.  But we can do them
> easily within standard C++ as demonstrated in the program below.

Your example (which I've snipped) certainly works (or looks like it
does), but it introduces some inefficiency. The nice thing about Ross's
proposal is that it does pretty much the same thing that the existing
cstdarg mechanism does, but in a type-safe manner (as long as all the
variable arguments are the same type). That is, the variable arguments
are already sitting on the stack in a neat row, so all we need, then, is
a pair of STL-style iterators that delimit these variable parameters,
which the proposed variadic<T> template provides nicely. The
implementation hides the details of how these iterators are obtained
(the compiler would have to pass a hidden count parameter), and whether
they physically increment or decrement in response to operator++, just
as the macros in cstdarg hide the same details.

I like the idea.

--

Ciao,
Paul


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1998/11/29
Raw View
In article <365E3828.23CCBCE3@ix.netcom.com>, Paul D. DeRocco
<pderocco@ix.netcom.com> writes
>Are you serious? We have to wait ten years before anything new might be
>added to the language? I don't really know what's physically involved
>with "maintaining the current standard and producing whatever technical
>reports SC22 deems appropriate," but it has to be a heck of a lot easier
>than what's been going on over the past five years. Yet you seem to be
>suggesting that the standards cycle consists of five years of developing
>a standard, then five years of maintaining it and tweaking it, then five
>years of developing the next version of the standard, with no overlap.
>That doesn't make sense to me.

One purpose behind a standard is to provide stability, that does not
mean that people cannot explore extensions but it does mean that SC22 is
not going to consider changes other than repairs for ten years.  Of
course work can start earlier (if there are people to do it) but actual
changes cannot except in so far as a Technical Report allows 'optional'
extensions.  But no implementor is required to take any notice of those.



Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Pete Becker <petebecker@acm.org>
Date: 1998/11/29
Raw View
Paul D. DeRocco wrote:

> Francis Glassborow wrote:
> >
> > You have five years to gain experience, lobby implementors to try it
> > as an extension etc.  For the time being WG21 is tasked with
> > maintaning the current standard and producing whatever technical
> > reports SC22 deem appropriate.  WG21 will not even consider extensions
> > to the C++ standard until 2003 at the earliest with the hope of
> > delivery in 2008.

> Are you serious? We have to wait ten years before anything new might be
> added to the language? I don't really know what's physically involved
> with "maintaining the current standard and producing whatever technical
> reports SC22 deems appropriate," but it has to be a heck of a lot easier
> than what's been going on over the past five years. Yet you seem to be
> suggesting that the standards cycle consists of five years of developing
> a standard, then five years of maintaining it and tweaking it, then five
> years of developing the next version of the standard, with no overlap.
> That doesn't make sense to me.

C9X will be approved about ten years after the C standard was initially
approved. Believe it or not, it actually does make sense. Otherwise you
have the instability and uncertainty of Java -- every year there's a
different version, and some of your old code won't run any more.

--
Pete Becker
Dinkumware, Ltd.
http://www.dinkumware.com


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/11/29
Raw View
On 28 Nov 1998 23:56:48 GMT, Paul D. DeRocco <pderocco@ix.netcom.com> wrote:

>Your example (which I've snipped) certainly works (or looks like it
>does), but it introduces some inefficiency. The nice thing about Ross's
>proposal is that it does pretty much the same thing that the existing
>cstdarg mechanism does, but in a type-safe manner (as long as all the
>variable arguments are the same type). That is, the variable arguments
>are already sitting on the stack in a neat row, so all we need, then, is
>a pair of STL-style iterators that delimit these variable parameters,
>which the proposed variadic<T> template provides nicely. The
>implementation hides the details of how these iterators are obtained
>(the compiler would have to pass a hidden count parameter), and whether
>they physically increment or decrement in response to operator++, just
>as the macros in cstdarg hide the same details.

No, there is the possibility for the compiler to inline and optimize
the Arg<T,N> method so that it is maximally efficient too.  Eg,

     ( Arg<const char *>("hello") << "there" << "world" )
          . copy(ostream_iterator<const char*>(cout," "));

creates a Arg<...,3> object.  The sizeof of this object is
   3 * sizeof(const char *)
Note that Arg<...,3> contains an Arg<...,2> by value, not by
reference.  Then the 'copy' can be inlined.  In fact, after
inlining, construction of the Arg objects should be optimized
away and we should get,
     cout << "hello" << " " << "there" << " " << "world" << " ";

I'm pretty sure my compilers don't do this optimization ... yet!



>I like the idea.


--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Andrei Alexandrescu" <alexandrescua@micromodeling.com>
Date: 1998/11/25
Raw View
Ross Smith wrote in message <73fbsh$t6v$1@newsource.ihug.co.nz>...
>Now that the first C++ standard is set in stone, we can start to
>consider possible additions to the next version. It won't be created
>until 2003 at the earliest, so we've got plenty of time to consider any
>new features carefully -- with no danger that anyone might actually try
>to standardise them yet.
>
>Here's one: safe variable argument lists.


[snip]

Very thorough proposal.
However, it allows only for functions with the same type for all variable
arguments. But there is a simple and elegant solution for this. Please see
my article in C++ Users Journal on September 1998, "Inline Containers for
Variable Arguments". We've used this is a medium-sized project with
excellent results.

Variable arguments are indeed a nasty loophole in the type system, but they
should be addressed in all their variety. Here templates have to play an
essential role.

Andrei




[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: sbnaran@localhost.localdomain.COM (Siemel Naran)
Date: 1998/11/25
Raw View
On 25 Nov 98 05:58:39 GMT, Ross Smith <ross.s@ihug.co.nz> wrote:

>Here's one: safe variable argument lists.

In your propasal below, each element in the variable list has the
same type.  Homogenous lists like these are the most common I
think.  They arise whenever we want to create (usually short)
arrays on the fly, as you've pointed out.  But we can do them
easily within standard C++ as demonstrated in the program below.

However, I like the idea of applying variable argument lists to
templates.  I have no idea what the syntax might look like, or
how we would retrieve template args, or how we would write the
code that uses these unknown arguments, but it's something to
think about.


>ADDITIONS TO THE STANDARD:
>
>* One addition to existing syntax rules.
>* No new keywords, operators, or other special tokens.
>* One new standard header.
>* Two new names in namespace std.

It would be nice if the class Arg below were part of the standard
and if class vector had a ctor
   template <size_type N> vector<T>::vector(const Arg<T,N>&);



// args.c
// by Siemel Naran
// class args<int>
// Class to generate variable length arrays on the fly


#include <algorithm> // fill

namespace myspace
{

template <typename T, int N>
class args
{
     private:
          args<T,N-1> d_first;
          T d_rest;

     public:
          args(const args<T,N-1>& first, T rest) : d_first(first), d_rest(rest)
          {
          }

          template <class U> void for_each(U u) const
          {
               d_first.for_each(u);
               u(d_rest);
          }

          template <class Iter> Iter copy(Iter dest) const
          {
               dest=d_first.copy(dest);
               *dest=d_rest;
               return ++dest;
          }
};

template <typename T>
class args<T,1>
{
     private:
          T d_rest;

     public:
          explicit args(T rest) : d_rest(rest)
          {
          }

          template <class U> void for_each(U u) const
          {
               u(d_rest);
          }

          template <class Iter> Iter copy(Iter dest) const
          {
               *dest=d_rest;
               return ++dest;
          }
};

template <typename T, int N>
inline args<T,N+1> operator<<(const args<T,N>& first, T rest)
{
     return args<T,N+1>(first,rest);
}

} // namespace myspace


/////
/////
/////

#include <iostream>

struct print
{
     void operator()(size_t x) { cout << x << ' '; }
};

const size_t N=3;

void action(const myspace::args<size_t,N>& arg)
{
     arg.for_each(print());
     cout << '\n';

     size_t array[N];
     std::fill(array,array+N,0);
     arg.copy(array);
     for (int i=0; i<N; i++) print()(array[i]);
     cout << '\n';
}

int main()
{
     typedef myspace::args<size_t,1> args;
     action(args(2u)<<3u<<4u); // output "2 3 4"
}
--
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: Francis Glassborow <francis@robinton.demon.co.uk>
Date: 1998/11/25
Raw View
In article <73fbsh$t6v$1@newsource.ihug.co.nz>, Ross Smith
<ross.s@ihug.co.nz> writes
>Now that the first C++ standard is set in stone, we can start to
>consider possible additions to the next version. It won't be created
>until 2003 at the earliest, so we've got plenty of time to consider any
>new features carefully -- with no danger that anyone might actually try
>to standardise them yet.


You have five years to gain experience, lobby implementors to try it as
an extension etc.  For the time being WG21 is tasked with maintaning the
current standard and producing whatever technical reports SC22 deem
appropriate.  WG21 will not even consider extensions to the C++ standard
until 2003 at the earliest with the hope of delivery in 2008.


Francis Glassborow      Chair of Association of C & C++ Users
64 Southfield Rd
Oxford OX4 1PA          +44(0)1865 246490
All opinions are mine and do not represent those of any organisation


[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]






Author: "Ross Smith" <ross.s@ihug.co.nz>
Date: 1998/11/25
Raw View
Now that the first C++ standard is set in stone, we can start to
consider possible additions to the next version. It won't be created
until 2003 at the earliest, so we've got plenty of time to consider any
new features carefully -- with no danger that anyone might actually try
to standardise them yet.

Here's one: safe variable argument lists.


RATIONALE:

C++ inherits from C a primitive facility for variadic functions
(functions with a variable number of arguments), supported by the header
<cstdarg>. The main drawback with the <cstdarg> mechanism is its
complete lack of type or range checking. If the user of a variadic
function passes arguments of an unexpected type, or misuses whatever
mechanism the function implementer provided to indicate the end of the
argument list (normally either a special sentinel value, or a count
provided as a named argument), results are undefined and probably
catastrophic.

Example:

    #include <cstdarg>
    #include <iostream>
    #include <string>
    void write(int first, ...) {
      int arg(first);
      va_list arguments;
      va_start(arguments, first);
      while (arg != -1) {
        std::cout << ' ' << arg;
        arg = va_arg(arguments, int);
      }
      va_end(arguments);
      std::cout << std::endl;
    }
    int main() {
      write(1, 2, 3, -1);   // OK
      write(1, 2, 3.0, -1); // Oops -- wrong argument type
      write(1, 2, 3, -11);  // Oops -- wrong sentinel
      return 0;
    }

This is the only facility for variadic functions available in C++98.
Because of its extreme lack of safety, it is hardly ever used (many
style guides advise programmers never to use it), even though there are
situations where variadic functions would be useful.

One situation is initialisation of containers with a short list of
values. Currently this must be done either by inserting them one at a
time, or by the use of an intermediate C-style array:

    std::vector<char> vowels;
    char init_vowels[] = { 'A', 'E', 'I', 'O', 'U' };
    std::copy(init_vowels, init_vowels + 5, std::back_inserter(vowels));
    // or...
    std::vector<char> vowels;
    vowels.push_back('A');
    vowels.push_back('E');
    vowels.push_back('I');
    vowels.push_back('O');
    vowels.push_back('U');

It would be handy to be able to simply write:

    std::vector<char> vowels('A', 'E', 'I', 'O', 'U');

Another potential application is multi-dimensional indexing. Suppose we
want to define an array template with arbitrary dimensions:

    template <typename T, unsigned Dim> class Array {
      // ...
    };

Giving this template suitable constructors, indexing operators, and
slicing functions -- all of which would expect to find a list of
dimensions or indices among their arguments -- is difficult. We can only
provide functions for a fixed number of dimensions. Ideally we would
like to provide all the dimensions at once, but there is no way to do
this unless we are willing to live with the hazardous <cstdarg>
mechanism or pass a container or iterator range in the arguments, which
can be awkward (and basically transforms this problem into the one
above).

In this particular case even fully safe variadic functions are still not
quite enough, since we still need to check that the number of arguments
matches the number of dimensions for the constructors and indexing
operators, or is less than or equal to it for the slicing functions.
It's hard to see how this could be done syntactically, but if we had
safe variadic functions and could simply throw an exception if the wrong
number of arguments was supplied, it would be less than perfect but an
improvement on the current situation.


PROPOSAL:

The proposed syntax for typesafe variadic functions (TVFs) involves one
addition to the standard function declaration syntax, one new standard
header file, and one new function in the existing <algorithm> header.

A TVF is declared by preceding the last formal argument in a function
declaration with an ellipsis (this argument will be referred to as the
"variadic formal argument"). This may be done with any function, either
at namespace scope or a class or struct member function (static or
non-static). A TVF may be a function template.

When calling such a function, any sequence of zero or more arguments
(the "variadic argument list") may be substituted for the variadic
formal argument. Calls to TVFs follow all the normal argument matching
and overload resolution rules; these are not changed in any way by this
proposal, and no special rules for variadic arguments are necessary.

Example:

    extern void vf1(int i, ... double d);
    class C {
      public:
        int vf2(... const std::string& s) const;
    };
    vf1(1, 2.0, 3.0); // OK
    vf1(1, 2, 3);     // OK, can convert int to double
    vf1(1, "2.0");    // Error, can't convert const char* to double
    C c;
    c.vf2("hello");   // OK, can convert const char* to std::string
    c.vf2(1234);      // Error, can't convert int to std::string
    c.vf2();          // OK

[Note: Unlike C-style variadic functions using the <cstdarg> mechanism,
TVFs are not required to have at least one non-optional argument; the
variadic formal argument may be the only formal argument, and such a
function may be called with an empty argument list.]

The header <variadic> declares one class template, std::variadic, with
the following public interface:

    namespace std {
      template <typename T> class variadic {
        public:
          typedef T value_type;
          typedef implementation_defined_1 const_reference;
          typedef implementation_defined_2 const_iterator;
          typedef implementation_defined_3 difference_type;
          typedef implementation_defined_4 size_type;
          const_iterator begin() const;
          const_iterator end() const;
          bool empty() const;
          size_type size() const;
          const_reference operator[](size_type) const;
          const_reference at(size_type) const;
      };
    }

The requirements for the members of std::variadic are the same as for
the corresponding members of a sequence, as defined in chapter 23 of the
C++98 standard.

[Note: The documented public interface contains no constructors,
destructors, or assignment operators. The class is not required to be
copyable, and its construction and destruction are up to the
implementation.]

If the definition of a TVF is encountered when the header <variadic> has
not been included, the program is ill-formed.

Within the body of a TVF's definition, the name of the variadic formal
argument does not refer to an object of that argument's declared type,
but to an instantiation of the template std::variadic for that type. The
contents of the sequence are the members of the variadic argument list.

Example:

    #include <numeric>
    #include <variadic>
    template <typename T> inline T average(... T t) {
      // Now t has the type std::variadic<T>
      return std::accumulate(t.begin(), t.end(), T()) / t.size();
    }

One new standard algorithm is declared in <algorithm>:

    namespace std {
      template <typename OutputIterator, typename T>
          OutputIterator var_copy(OutputIterator target, ... T t);
    }

    Effects: Copies each of the objects in the variadic argument list
    into successive locations beginning with *target.

    Returns: target plus the number of items in the variadic argument
    list.

    Complexity: The number of assignments is equal to the size of the
    variadic argument list.

[Note: A typical implementation would be:

    namespace std {
      template <typename OutputIterator, typename T> inline
          OutputIterator var_copy(OutputIterator target, ... T t) {
        return copy(t.begin(), t.end(), target);
      }
    }

This can be used to initialise a sequence:

    std::vector<char> vowels;
    std::var_copy(std::back_inserter(vowels), 'A', 'E', 'I', 'O', 'U');

Adding variadic constructors to the standard sequences would introduce
overload resolution problems.]


ALTERNATIVES:

The fact that a formal argument ends up with a type other than the one
it was apparently declared with may be seen as undesirable. An
alternative is to ignore the formal name of the variadic argument (or
require it not to have one), and give the corresponding argument list a
standard name, e.g. std::var_arguments. The example above would then
become:

    #include <numeric>
    #include <variadic>
    template <typename T> inline T average(... T) {
      return std::accumulate(std::var_arguments.begin(),
                             std::var_arguments.end(), T())
             / std::var_arguments.size();
    }

However, the identifier std::var_arguments would have very odd
properties from the compiler's point of view. I prefer the syntax
described in the main proposal.


ADDITIONS TO THE STANDARD:

* One addition to existing syntax rules.
* No new keywords, operators, or other special tokens.
* One new standard header.
* Two new names in namespace std.


IMPACT ON EXISTING CODE:

Minimal; possibly none.

The new syntax is currently illegal, and the new names are in namespace
std. The only currently legal programs that might be affected are those
that use a header named <variadic> or an identifier var_copy. Such
programs are probably very rare to nonexistent (if they turn out to be
surprisingly common, we can always pick different names).

--
Ross Smith ................................... mailto:ross.s@ihug.co.nz
.............. The Internet Group, Auckland, New Zealand ..............
                               * * * * *
       "We're bored. We're armed. And we're off the medication."
---
[ 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://reality.sgi.com/austern_mti/std-c++/faq.html              ]