Overloading vs C++ 0x variadic templates

Discussion in 'C++' started by er ci, Aug 13, 2010.

  1. er ci

    er ci Guest

    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)?
     
    er ci, Aug 13, 2010
    #1
    1. Advertising

  2. On Aug 13, 4:11 pm, er ci <> wrote:
    > 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
     
    Howard Hinnant, Aug 13, 2010
    #2
    1. Advertising

  3. er ci

    bx12 Guest


    > 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.
     
    bx12, Aug 14, 2010
    #3
  4. er ci

    er Guest

    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.
     
    er, Aug 14, 2010
    #4
  5. Howard Hinnant <> wrote:
    > #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?
     
    Juha Nieminen, Aug 14, 2010
    #5
  6. er ci

    Luc Danton Guest

    On 14/08/2010 10:11, Juha Nieminen wrote:
    > 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.
     
    Luc Danton, Aug 14, 2010
    #6
  7. On Aug 14, 2:24 am, er <> wrote:
    > 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
     
    Howard Hinnant, Aug 14, 2010
    #7
  8. On Aug 14, 5:56 am, Luc Danton <> wrote:
    > On 14/08/2010 10:11, Juha Nieminen wrote:
    >
    > >    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 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
     
    Howard Hinnant, Aug 14, 2010
    #8
  9. er ci

    er Guest

    > 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?
     
    er, Aug 14, 2010
    #9
  10. er ci

    er Guest

    > template<typename U> f(U&);
    >
    > // n = 1
    > template<typename U> f(U&); // U = T or T const


    PS: The redundancy above is non-intentional. There should be only one
    definition for f.
     
    er, Aug 14, 2010
    #10
  11. On Aug 14, 11:26 am, er <> wrote:
    > > 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?


    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
     
    Howard Hinnant, Aug 14, 2010
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Colin Walters
    Replies:
    2
    Views:
    543
    Ben Pfaff
    Feb 13, 2004
  2. Ross A. Finlayson
    Replies:
    19
    Views:
    650
    Keith Thompson
    Mar 10, 2005
  3. Replies:
    2
    Views:
    366
    Dave Thompson
    Feb 27, 2006
  4. Replies:
    5
    Views:
    385
  5. recover
    Replies:
    2
    Views:
    857
    recover
    Jul 25, 2006
Loading...

Share This Page