4

In C++11, variadic templates allows one to call a function with any number of arguments, and the ellipsis operator ... allows that variadic function to do something to each of those arguments, even if that something isn't the same thing for each argument:

template<typename... Types>
void dummy(Types... ts){} //dummy to allow function calls via parameter expansion

template<typename... Numerics>
void increment5(Numerics&... ns){
    dummy(ns+=5 ...); //parameter expansion (need space after 5 because 5. is decimal)
    //it means "dummy(ns_first+=5, ns_second+=5, ns_third+=5, etc.)"
}

int main(){
    int i = 0;
    float f = 1.1;
    std::valarray<int> vi = {1,2,3};

    increment5(i,f,vi);

    cout
        <<i<<endl
        <<f<<endl
        <<vi[0]<<' '<<vi[1]<<' '<<vi[2]<<endl;

}

If we define a heterogenous value array (a list of different number types), we want to be able to do the same kind of thing (being able to add a number to each of its elements). But we have to store the elements as a tuple.

//a class of numbers, possibly different
template<typename... Numerics>
class HeterogeneousValueArray{
private:
    tuple<Numerics...> inner;
public:
    //initialize the internal vector
    HeterogeneousValueArray(Numerics... ns): inner(ns...) {}
};

//Given this function, we don't have to explicitly give the types
//when constructing a HVA
template<typename... Numerics>
HeterogeneousValueArray<Numerics...> HeterogeneousValueArray(Numerics... ns){
    return HeterogeneousValueArray<Numerics...>(ns);
}

To call the increment5 operator above, we would need to do a tuple expansion. As I understand it, this solution would require defining helper functions for each function I want to write. We can also define the increment5 recursively, but that, again, requires two function bodies per function.

I believe that programming languages should strive to a design that allows us to write the code we want to write. So this is what I want to write.

template<typename... Numerics>
void increment5(HeterogeneousValueArray<Numerics...>& hva){
    increment5(hva.inner... ); //expand a tuple
}

Or this.

template<typename... Numerics>
void increment5(HeterogeneousValueArray<Numerics...>& hva){
    dummy((hva.inner+5)... ); //expand a tuple
}

In other words, I want to treat a tuple as a parameter pack.

Of course, "write the code you want to write" is idealistic, and there could be problems implementing any feature. What kind of issues would make this kind of feature not work as intended (ambiguity?), or how would it step on the toes of existing code or features? Or... does it already exist in C++14?

Community
  • 1
  • 1
leewz
  • 3,201
  • 1
  • 18
  • 38
  • 1
    I don't think you *need* to write a helper function for each function you want to call on the expanded tuple. You can just use a function template that takes a tuple and a function object (including function pointers), and applies the function object to each element of the tuple. – dyp Dec 21 '13 at 19:39
  • Oh yeah, I don't think you can use overloaded functions like that, either. You would have to make a helper function in that case. – leewz Dec 21 '13 at 20:00
  • For overloaded functions, you can use a generic lambda in C++1y. It's also kind of a helper function, but with the usual benefits of lambdas (locality, simple to write). – dyp Dec 21 '13 at 21:32

1 Answers1

5

The basic tools for creating the solution exist in C++14 but you do need an extra level of indirection via an extra helper function, similar to the answer you linked to:

template<typename Tuple, std::size_t... I>
void
_increment5(Tuple& t, std::index_sequence<I...>)
{
  dummy( std::get<I>(t) += 5 ... );
}

template<typename... Numerics>
void
increment5(HeterogeneousValueArray<Numerics...>& hva)
{
  _increment5(hva.inner, std::index_sequence_for<Numerics...>());
}

std::index_sequence_for<T1, T2, T3> is an alias for the type std::index_sequence<0, 1, 2>, so when used with a pack expansion it creates a sequences of indices for the elements of the parameter pack. That sequence of indices can then be deduced as another parameter pack by the helper function, so the pack expansion std::get<I>(t)... will expand I to extract each element of the tuple.

The proposal that added index_sequence to C++14 also added an example showing a generic apply function for applying a function object to tuples, which would allow what @DyP suggested in a comment above:

template<typename... Numerics>
void
increment5(HeterogeneousValueArray<Numerics...>& hva)
{
  apply(hva.inner, [](auto& n) { n += 5; });
}

The apply function is not in C++14 (but has been proposed for C++17) so you'd need to write it yourself, e.g. by copying it from the C++14 draft.

The closest proposal that would directly allow what you want is N3728 but that has not been accepted, so will not be in C++14.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • You dont need C++14, you can create yourself `std::index_sequence` in C++11. All required language features are in. – Yankes Dec 22 '13 at 21:08
  • Yes I know, I wrote the proposal to add it to the standard :) But in C++14 you don't have to do that, the implementation provides it. – Jonathan Wakely Dec 22 '13 at 21:10
  • N3728 seems to be what I want (not tuples, but stored parameter packs), but doesn't explain if adding the syntax to tuples would cause issues. – leewz Dec 23 '13 at 01:00