Variable size parameter

Feb 10, 2011 at 3:59am
I had a really cool idea today, but I can't get it to work.

I'm trying to make a class similar to va_list, but it stores the number of variable arguments passed. That way, you don't have to have an extra parameter to tell you how many arguments there are. To do this, I planned on overloading the "," operator to push back arguments of any type onto a queue. Functions would be declared/called like this:

1
2
3
type foo(vparam bar=vparam());
//...
foo(46,12,0,384);

However, this all requires one very crucial thing to be valid: I must be able to pass a value of arbitrary type instead of the variable parameter type, which would be passed to the constructor. If this doesn't work, the first value of the variable parameter type wouldn't be passed as a vparam, and a "," overload wouldn't work. While this seems to work for non-templated functions, templated functions say "foo(type) was not declared in this scope"

Does anyone know how to do this, or if it's even possible? Otherwise, it could get cumbersome...

foo(vparam(firstvalue),38,20,"hello");
Feb 10, 2011 at 1:10pm
This is not possible. As far as I am concerned, the best syntax for this kind of feature in C++ is:

foo(list_of(firstvalue)(38)(20)("hello"));

or if you doesn't need indefinitely large number of arguments:

foo(list_of(firstvalue, 38, 20, "hello"));
Last edited on Feb 10, 2011 at 1:14pm
Feb 10, 2011 at 2:26pm
There might be something we can do, however a question I have is how do you expect
the receiving function to access the arguments? (ie, what syntax)?
Feb 10, 2011 at 2:53pm
Quick example I just whipped up:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <boost/any.hpp>
#include <iostream>
#include <vector>

struct varargs
{
  private:
    typedef boost::any              argument;
    typedef std::vector< argument > container;

  public:
    typedef container::size_type    size_type;

    varargs() : args() {}

    template< typename T >
    explicit varargs( const T& t ) : args( 1, argument( t ) ) {}

    template< typename T >
    varargs& operator,( const T& t )
        { args.push_back( argument( t ) ); return *this; }

    template< typename T >
    T get( size_t idx )
        { return boost::any_cast<T>( args[ idx ] ); }

    size_type size() const
        { return args.size(); }

  private:
    container args;
};


void func( const char* str, varargs v )
{
    std::cout << str << std::endl;
    int a = v.get<int>( 0 );
    char b = v.get<char>( 1 );
    std::string c = v.get<std::string>( 2 );
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;
    std::cout << "c = " << c << std::endl;
}

int main()
{
    func( "Some string", ( varargs( 42 ), 'b', std::string( "Hello World" ) ) );
}


Note that the calling convention of func() requires that the varargs parameter be explicitly parenthesized, which
kind of sucks.

An alternative is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct varargs {
  private:
    typedef boost::any              argument;
    typedef std::vector< argument > container;

  public:
    typedef container::size_type    size_type;

    varargs() : args() {}

    template< typename T1 >
    explicit varargs( const T1& t ) : args( 1, t ) {}

    template< typename T1, typename T2 >
    varargs( const T1& t1, const T2& t2 ) { /* ... */ }

    template< typename T1, typename T2, typename T3 >
    varargs( const T1& t1, const T2& t2, const T3& t3 ) { /* ... */ }

    // And so forth, up to the maximum number of variadic parameters you wish to support

    template< typename T >
    T get( size_t idx )
        { return boost::any_cast<T>( args[ idx ] ); }

    size_type size() const
        { return args.size(); }

  private:
    container args;
};

void func( const char* str, varargs v )
{
    std::cout << str << std::endl;
    int a = v.get<int>( 0 );
    char b = v.get<char>( 1 );
    std::string c = v.get<std::string>( 2 );
    std::cout << "a = " << a << std::endl;
    std::cout << "b = " << b << std::endl;
    std::cout << "c = " << c << std::endl;
}

int main()
{
    func( "Some string", varargs( 42, 'b', std::string( "Hello World" ) ) );
}


And now we just moved the parens on the varargs parameter.
Feb 11, 2011 at 4:01am
It's funny, because I actually tried making it contain any containers when I was experimenting with it. So, it needs parenthesis around it? That really sucks... Oh well, I guess this idea is a bust.
Feb 11, 2011 at 1:50pm
Either way it does. The second way because we use the varargs constructor. The first way because the
compiler isn't smart enough to realize that in the expression

func( a, b, c, d )

a is mapped to the first parameter of func whereas a varargs should be implicitly constructed from b so that
all subsequent parameters can be stuffed into the varargs via the , operator.
Feb 11, 2011 at 2:40pm
I guess you may know this, but I have to mention - C++0x will have variadic templates. This will probably work much better for a general packing/unpacking routine and also for a stub function in front of the actual call. Template code bloat is the only remaining concern. For the time being I kind-of like the above solutions. The world is not perfect.

EDIT: Forgot to mention initializer lists. They will allow you to write stuff like max({1, 2, 3}). The arguments must be of the same type, but it is nice for functions that process sets of stuff.
Last edited on Feb 11, 2011 at 2:43pm
Feb 11, 2011 at 3:28pm
Yes, variadic templates eliminates the gazillion constructors and the arbitrary limitation on the number of arguments supported, but it doesn't solve the underlying problem in that use of varargs requires extra syntax at the call point. But the worst part about these solutions, in my opinion, is the fact that I have to know the types of the arguments in order to extract them from the object. I suppose this would be OK for a typesafe C++ version of printf, but to pass anything other than compiler intrinsic types (PODs) through a varargs object would require func() to be modified to know of the additional types, which is, in itself fairly limiting.

One way around that problem would be to use boost::variant instead of boost::any as the argument holder. However, the actual variant type would need to be constructed from the callpoint, otherwise variant itself has a limited number template parameters, and this would be overly restrictive to the user. Then, func() would need to use static_visitor objects to access the parameters in a typesafe way, which would lead to a completely different design of func(). It would be an interesting exercise nonetheless.

Feb 11, 2011 at 4:05pm
I understand you have given this some thought. I'm not going to pretend that I am well versed for boost conversation, but apparently the variant mechanism is a bit irky in the grand scheme of things in order to make it robust. It would be nice to have type-safe hierarchy-independent robust polymorphic processing, to which I can not see even the conceptual solution. Alas, I am used to the static approach to things.

Regards
Last edited on Feb 11, 2011 at 4:06pm
Topic archived. No new replies allowed.