0

Please consider the following program:

#include <iostream>


template <typename T, typename ...Ts>
struct Foo {
    template <typename ...Us>
    static void bar(T& oT0, T& oT1, const T& iT0, const T& iT1, Us... args) {
        std::cout << " -> Foo<...>::bar() enter [ " << oT0 << ", " << oT1 << " ]" << std::endl;
        Foo<T>::bar(oT0, oT1, iT0, iT1);
        Foo<Ts...>::bar(args...);
        std::cout << " <- Foo<...>::bar() exit [ " << oT0 << ", " << oT1 << " ]" << std::endl;
    }
};

template <typename T>
struct Foo<T> {
    static void bar(T& oT0, T& oT1, const T& iT0, const T& iT1) {
        std::cout << " -> Foo<>::bar() enter [ " << oT0 << ", " << oT1 << " ]" << std::endl;
        oT0 = iT0;
        oT1 = iT1;
        std::cout << " <- Foo<>::bar() exit [ " << oT0 << ", " << oT1 << " ]" << std::endl;
    }
};


int main() {
    int i0 = -1,
        i1 = 0;
    float f0 = -97.18f,
          f1 = 3.141592f;
    std::cout << "( "<< i0 << ", " << i1 << "; " << f0 << ", " << f1 << " ) " << std::endl;

    Foo<int, float, int>::bar(i0, i1, 0, 1, f0, f1, 18.f, -7.f, i0, i1, 4, 17);
    std::cout << "( "<< i0 << ", " << i1 << "; " << f0 << ", " << f1 << " ) " << std::endl;

    Foo<float>::bar(f0, f1, 18.f, -7.f);
    std::cout << "( " << f0 << ", " << f1 << " ) " << std::endl;

    Foo<float, int>::bar(f0, f1, 2.71f, 9000.1f, i0, i1, 4, 17);
    std::cout << "( "<< i0 << ", " << i1 << "; " << f0 << ", " << f1 << " ) " << std::endl;

    return 0;
}

And its commented output (debug output removed for clarity but available at IDEone):

( -1, 0; -97.18, 3.14159 ) // initial values
( 0, 1; -97.18, 3.14159 ) // ints only set once?! floats unchanged?!
( 18, -7 ) 
( 0, 1; 2.71, 9000.1 ) // ints unchanged?!

I must be missing something obvious here: from the above, calling Foo<...>::bar(...) only modifies the first set of two non-const parameters. Why are the values of the next arguments left untouched within main?

dummydev
  • 423
  • 3
  • 14
  • Have you stepped through with a debugger? – Steve Sep 12 '16 at 18:58
  • 4
    Do you mean `Us& ... args` rather than `Us ... args`? – Jason C Sep 12 '16 at 18:59
  • 3
    @JasonC `Us&& ... args`, really. It needs to deal with const lvalues too – jaggedSpire Sep 12 '16 at 19:02
  • @Steve: actually I didn't, it simply did not occur to me that the passed addresses were different (it should!). – dummydev Sep 12 '16 at 19:10
  • @jaggedSpire: you nailed it! I'm not familiar with this syntax, would you happen to have a reference at hand? You may also had this as an answer. TBH I thought that the functions signature were enough to infer the expanded types, I'm obviously still mistaken about how parameter packs work. – dummydev Sep 12 '16 at 19:14
  • @jaggedSpire `Us&...` can deal with const lvalues. – Barry Sep 12 '16 at 19:15
  • I updated the title to better fit the cause of the error. BTW, care to explain the downvote? – dummydev Sep 12 '16 at 19:17
  • @dummydev: [My answer](http://stackoverflow.com/a/39457174/364696) demonstrates the required syntax, and links to [a useful question on perfect forwarding for templated varargs](http://stackoverflow.com/a/2821244/364696). That work for you? – ShadowRanger Sep 12 '16 at 19:19
  • Hi, for my opinion the instatiation af the bar-function is missing. I would expect sth like Foo::bar. Which would be your 'Us ...' types. But you said it works for you. So... did you set breakpoints in both bar-functions? – Bernhard Heinrich Sep 12 '16 at 19:20
  • @Barry not sure exactly how to describe it in language lawyer terms, but the binding needs to be to a [universal reference here](http://coliru.stacked-crooked.com/a/00eb02b961d0bbb9), at least according to clang and gcc – jaggedSpire Sep 12 '16 at 19:23
  • @Barry: I think the problem would be const rvalues, but maybe I'm mistaken. Presumably, if you just received as non-`const` references, then passing unnamed rvalues for the `const` reference components (e.g. in given code, `18.f, -7.f`) [would cause a problem](http://stackoverflow.com/q/4084053/364696). I don't know the C++11 spec well enough to say there aren't weird exceptions for varargs cases I'll admit; could be the compiler should just make temporary storage and ignore the fact that mutations to the referenced argument are lost. – ShadowRanger Sep 12 '16 at 19:24
  • @Bernhard: the IDEone link above shows the complete output from stdout (I omitted it here for brievity) and confirms control reaches both definitions. – dummydev Sep 12 '16 at 19:24

1 Answers1

4

You need to use references on the varargs part of the template or the arguments beyond the first four will be passed to the initial call by value (copying to local variables). The subsequent calls will mutate those copied values, not the original arguments passed.

Since you want to accept rvalues for some of the arguments, use perfect forwarding for varargs to preserve the original types. Just change:

static void bar(T& oT0, T& oT1, const T& iT0, const T& iT1, Us... args) {

to:

// Receive varargs as forwarding references to allow perfect forwarding instead
// of receiving them as copies
static void bar(T& oT0, T& oT1, const T& iT0, const T& iT1, Us&&... args) {

and change:

Foo<Ts...>::bar(args...);

to:

// Per earlier link, must explicitly template std::forward call in varargs case
Foo<Ts...>::bar(std::forward<Us>(args)...); 

which should receive and forward the varargs correctly, so they are received in the nested calls as whatever type (const or non-const reference) the innermost "real" call requires.

Community
  • 1
  • 1
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I thought I had the grasp on variadic templates, turns out I have a lot more reading to do.. thanks. – dummydev Sep 12 '16 at 19:22