Overloading vs C++ 0x variadic templates

E

er ci

Hello,

I have a function g, overloaded on # of arguments, which forwards to
f:

template<typename F>
struct foo{

foo(){}

void g(){ this->f(); }
template<typename T0> void g(T0& a){ this->f( a ); }
template<typename T0,typename T1> void g(T0& a,T1& b){ this->f( a,
b ); }
// etc. a certain number of times. Usually this is done with a pp
macro.
private:
F f;
};

f need not be defined for each overload, only those that are actually
called.

I was hoping, naively as it turns out, that in C++0x, the above could
be replaced by

template<typename... Args>
void g(Args... args){ this->f(args); }

but the compiler says "parameter pack not expanded with".

Any suggestion, please (besides learning C++0x, thank you very much,
that's what I'm trying to do here)?
 
H

Howard Hinnant

Hello,

I have a function g, overloaded on # of arguments, which forwards to
f:

template<typename F>
struct foo{

   foo(){}

   void g(){ this->f(); }
   template<typename T0> void g(T0& a){ this->f( a ); }
   template<typename T0,typename T1> void g(T0& a,T1& b){ this->f( a,
b ); }
   // etc. a certain number of times. Usually this is done with a pp
macro.
  private:
   F f;

};

f need not be defined for each overload, only those that are actually
called.

I was hoping, naively as it turns out, that in C++0x, the above could
be replaced by

template<typename... Args>
void g(Args... args){ this->f(args); }

but the compiler says "parameter pack not expanded with".

Any suggestion, please (besides learning C++0x, thank you very much,
that's what I'm trying to do here)?

This ought to do it:


#include <utility>

template<typename F>
struct foo{
foo(){}

template<typename... Args>
void g(Args&&... args)
{ this->f(std::forward<Args>(args)...); }
private:
F f;
};

Explanation: You were lacking "args...". Also you're lacking
"perfect forwarding" which is what the "Args&&" and "forward<Args>" is
all about. In a nutshell, the perfect forwarding will duplicate in
the call to f(), the lvalue/rvalue-ness and cv-quals present in the
call to g(). This is important for move semantics: temporaries sent
to g() can be moved from within f().

-Howard
 
B

bx12

This ought to do it:

#include <utility>

template<typename F>
struct foo{
   foo(){}

   template<typename... Args>
    void g(Args&&... args)
        { this->f(std::forward<Args>(args)...); }
private:
   F f;

};

Thanks, very useful.

As it is though, calling g with an rvalue, say g(5), will not work
unless f specifically caters to rvalues. So, I'd like to add that
twist to the initial problem. The old fashioned way it would be

template<typename F>
struct foo{

foo(){}

// n = 0
void g(){ this->f(); }

// n = 1
template<typename T0> void g(T0& a){ this->f<T0>( a ); }
template<typename T0> void g(T0 const& a){ this->f<T0
const>( a ); }

// n = 2
template<typename T0,typename T1> void g(T0& a,T1& b){
this->f( a, b );
}
template<typename T0,typename T1> void g(T0& a,T1 const& b){
this->f<T0, T1 const>( a,b );
}
// and <T0 const, T1> and <T0 const, T1 const>

// n = 3,...,N Usually done with a pp macro.
private:
F f;

};


The point, here, is that f need not be overloaded on lvalue/const.
lvalue is enough:

template<typename T0> f(T0& );
template<typename T0,typename T1> f(T0&,T1&);
etc.

Now, how would Howard's code have to be modified to achieve this with C
++0x?

Thanks.
 
E

er

Thanks, very useful.

As it is though, calling g with an rvalue, say g(5), will not work
unless f specifically caters to rvalues. So, I'd like to add that
twist to the initial problem. The old fashioned way it would be

template<typename F>
struct foo{

foo(){}

// n = 0
void g(){ this->f(); }

// n = 1
template<typename T0> void g(T0& a){ this->f<T0>( a ); }
template<typename T0> void g(T0 const& a){ this->f<T0
const>( a ); }

// n = 2
template<typename T0,typename T1> void g(T0& a,T1& b){
this->f( a, b );
}
template<typename T0,typename T1> void g(T0& a,T1 const& b){
this->f<T0, T1 const>( a,b );
}
// and <T0 const, T1> and <T0 const, T1 const>

// n = 3,...,N Usually done with a pp macro.
private:
F f;

};

The point, here, is that f need not be overloaded on lvalue/const.
lvalue is enough:

template<typename T0> f(T0& );
template<typename T0,typename T1> f(T0&,T1&);
etc.

Now, how would Howard's code have to be modified to achieve this with
C
++0x?

Thanks.
 
J

Juha Nieminen

Howard Hinnant said:
#include <utility>

template<typename F>
struct foo{
foo(){}

template<typename... Args>
void g(Args&&... args)
{ this->f(std::forward<Args>(args)...); }
private:
F f;
};

Explanation: You were lacking "args...". Also you're lacking
"perfect forwarding" which is what the "Args&&" and "forward<Args>" is
all about. In a nutshell, the perfect forwarding will duplicate in
the call to f(), the lvalue/rvalue-ness and cv-quals present in the
call to g(). This is important for move semantics: temporaries sent
to g() can be moved from within f().

In an older thread someone claimed that the rvalue reference
operator && will not, after all, work with lvalues in C++0x. If
that is so, then I don't understand how the above code can work
(except if *all* the parameters happen to be rvalues). Or is
"&&..." somehow a special case?
 
L

Luc Danton

In an older thread someone claimed that the rvalue reference
operator&& will not, after all, work with lvalues in C++0x. If
that is so, then I don't understand how the above code can work
(except if *all* the parameters happen to be rvalues). Or is
"&&..." somehow a special case?

int&& i = 5; // Binds ok

int&& j = i;
// Note that the name 'i' is an lvalue
// So this one doesn't bind

This also applies for function parameters:
void func(int&& i);

func(5); // Ok
int i = 42;
func(i); // Not ok

There *is* a special rule, and that's in a template deduction context
(not variadic templates):
template<typename T>
void func(T&& t); // Will eat anything you throw at it...

int lvalue = 4;
int const& clvalue = lvalue;

func(lvalue);
// Ok, T&& deduced to int& &&, collapses to int&
// func<int&> called

func(clvalue);
// Ok, T&& deduced to int const& &&,
// collapses to const int&
// func<const int &> called

func(std::move(lvalue))
// Ok, T&& deduced to int &&, no collapsing needed
// func<int> called
// Note that int&& && would collapse to int&&

So there really are two flavours of 'T&&': for rvalue references (which
is quite straightforward), and for perfect-forwarding, which is designed
to be used idiomatically and is powerful, esp. when combined with
std::forward, but actually needs some extra rules to make it work. I
guess we should really tell the two apart, e.g. calling 'int&& i' a
rvalue reference and 'template<typename T> ... T&& t' a perfect
reference or something.

If you're wondering, no practical use was found for const rvalues so
they got left behind; don't use them.
 
H

Howard Hinnant

Thanks, very useful.

As it is though, calling g with an rvalue, say g(5), will not work
unless f specifically caters to rvalues.

I'm probably misunderstanding your statement above. The way I'm
reading that statement it can be reworded like so:

Calling g with an rvalue, say g(5), won't work, unless it does
work.

Sorry, I'm really not meaning to be obstructive. I'm truly not
understanding your problem domain yet.
So, I'd like to add that
twist to the initial problem. The old fashioned way it would be

template<typename F>
struct foo{

   foo(){}

   // n = 0
   void g(){ this->f(); }

   // n = 1
   template<typename T0> void g(T0& a){ this->f<T0>( a ); }
   template<typename T0> void g(T0 const& a){ this->f<T0
const>( a ); }

   // n = 2
   template<typename T0,typename T1> void g(T0& a,T1& b){
        this->f( a, b );
   }
   template<typename T0,typename T1> void g(T0& a,T1 const& b){
     this->f<T0, T1 const>( a,b );
   }
   // and <T0 const, T1> and <T0 const, T1 const>

   // n = 3,...,N Usually done with a pp macro.
  private:
   F f;

};

The point, here, is that f need not be overloaded on lvalue/const.
lvalue is enough:

In the above formulation, g(5) can be called (binds to the const&
overload). Indeed, it is this 2^n "explosion" that perfect forwarding
is intended to avoid. Reference:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm#s3
template<typename T0> f(T0& );
template<typename T0,typename T1> f(T0&,T1&);
etc.

Now, how would Howard's code have to be modified to achieve this with
C
++0x?

If you would like to disallow rvalue arguments to g() you can do this:

template<typename F>
struct foo{
foo(){}

template<typename... Args>
void g(Args&... args)
{ this->f(args...); }
private:
F f;
};

But this isn't equivalent to your T0&, const T1& implementation as it
disallows g(5).

-Howard
 
H

Howard Hinnant

int&& i = 5; // Binds ok

int&& j = i;
// Note that the name 'i' is an lvalue
// So this one doesn't bind

This also applies for function parameters:
void func(int&& i);

func(5); // Ok
int i = 42;
func(i); // Not ok

There *is* a special rule, and that's in a template deduction context
(not variadic templates):
template<typename T>
void func(T&& t); // Will eat anything you throw at it...

int lvalue = 4;
int const& clvalue = lvalue;

func(lvalue);
// Ok, T&& deduced to int& &&, collapses to int&
// func<int&> called

func(clvalue);
// Ok, T&& deduced to int const& &&,
// collapses to const int&
// func<const int &> called

func(std::move(lvalue))
// Ok, T&& deduced to int &&, no collapsing needed
// func<int> called
// Note that int&& && would collapse to int&&

So there really are two flavours of 'T&&': for rvalue references (which
is quite straightforward), and for perfect-forwarding, which is designed
to be used idiomatically and is powerful, esp. when combined with
std::forward, but actually needs some extra rules to make it work. I
guess we should really tell the two apart, e.g. calling 'int&& i' a
rvalue reference and 'template<typename T> ... T&& t' a perfect
reference or something.

If it helps anyone, here is a little more discussion of perfect
fowarding:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html#Perfect_Forwarding
If you're wondering, no practical use was found for const rvalues so
they got left behind; don't use them.

Actually a few use cases for const A&& are beginning to emerge. Here
is one example:

http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#688

-Howard
 
E

er

In the above formulation, g(5) can be called (binds to the const&
overload).  Indeed, it is this 2^n "explosion" that perfect forwarding
is intended to avoid.  Reference:

Yes, I'm aware that perfect forwarding caters to that, but here the
problem is with f, or rather the f--g interaction, not g alone. So
let me rephrase:

template<typename U> f(U&);

// n = 1
template<typename U> f(U&); // U = T or T const
template<typename T> g(T& t){ f( t ); }
template<typename T> g(T const & t){ f( t ); }
// etc. for n = 1,...,N

template<Args...> g_0x(Args&&...args)
{ f(std::forward<Args>( args )...); }
template<Args...> h_0x(Args&&...args)
{ f<Args...>( std::forward<Args>( args )... ); }


int main(){

g( 1 ); // ok
g_0x( 1 ); // invalid initialization of non-const ref from
temporary
h_0x( 1 ); // no matching function for call to f(int)
return 0;
}

What should be done to g/h _0x's definition for this to compile, and
if nothing can be done, what should be done to that of f?
 
H

Howard Hinnant

Yes, I'm aware that perfect forwarding caters to that, but here the
problem is with f, or rather the f--g interaction, not g alone.  So
let me rephrase:

template<typename U> f(U&);

// n = 1
template<typename U> f(U&); // U = T or T const
template<typename T> g(T& t){ f( t ); }
template<typename T> g(T const & t){ f( t ); }
// etc. for n = 1,...,N

template<Args...> g_0x(Args&&...args)
{ f(std::forward<Args>( args )...); }
template<Args...> h_0x(Args&&...args)
{ f<Args...>( std::forward<Args>( args )... ); }

int main(){

   g( 1 ); // ok
   g_0x( 1 ); // invalid initialization of non-const ref from
temporary
   h_0x( 1 ); // no matching function for call to f(int)
   return 0;

}

What should be done to g/h _0x's definition for this to compile, and
if nothing can be done, what should be done to that of f?

If this is the behavior you need, here is how I would code it:


// n = 1
template<typename U> void f(U&) {} // U = T or T const
template<typename T> void g(T& t) { f( t ); }
template<typename T> void g(T const & t) { f( t ); }

// etc. for n = 1,...,N
template<typename ...Args> void g_0x(Args&&...args)
{ f(args...); }

template<typename ...Args> void h_0x(Args&&...args)
{ f( args...); }

int main(){
g( 1 ); // ok
g_0x( 1 ); // ok
h_0x( 1 ); // ok
return 0;
}

Explanation: inside of g_0x (and h_0x) each args is treated as an
lvalue. I.e., even though it is declared with an rvalue reference
type, /named/ references, even rvalue references, are lvalues. So in
the forwarding call to f(), f() is only seeing lvalues now.

This behavior is a safety feature to prevent variables from getting
"moved from" more than once. For more info see:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#More on A&&

starting with:

"Even though named rvalue references ..."

Disclaimer: I'm pointing you to fairly old papers only because these
papers discuss the relevant details. Be forewarned that details of
the rvalue reference language feature have changed somewhat over the
years. If we stumble across a case where such a change matters, I'll
point it out.

-Howard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,012
Latest member
RoxanneDzm

Latest Threads

Top