How to make generic computations over heterogeneous argument packs of a variadic template function?
Asked Answered
I

5

76

PREMISE:

After playing around with variadic templates a little bit, I realized that achieving anything which goes slightly beyond the trivial meta-programming tasks soon becomes pretty cumbersome. In particular, I found myself wishing for a way to perform generic operations over an argument pack such as iterate, split, loop in a std::for_each-like fashion, and so on.

After watching this lecture by Andrei Alexandrescu from C++ and Beyond 2012 on the desirability of static if into C++ (a construct borrowed from the D Programming Language) I had the feeling that some sort of static for would come handy as well - and I feel more of these static constructs could bring benefit.

So I started wondering if there is a way to achieve something like this for argument packs of a variadic template function (pseudo-code):

template<typename... Ts>
void my_function(Ts&&... args)
{
    static for (int i = 0; i < sizeof...(args); i++) // PSEUDO-CODE!
    {
        foo(nth_value_of<i>(args));
    }
}

Which would get translated at compile-time into something like this:

template<typename... Ts>
void my_function(Ts&&... args)
{
    foo(nth_value_of<0>(args));
    foo(nth_value_of<1>(args));
    // ...
    foo(nth_value_of<sizeof...(args) - 1>(args));
}

In principle, static_for would allow for even more elaborate processing:

template<typename... Ts>
void foo(Ts&&... args)
{
    constexpr s = sizeof...(args);

    static for (int i = 0; i < s / 2; i++)
    {
        // Do something
        foo(nth_value_of<i>(args));
    }

    static for (int i = s / 2; i < s; i++)
    {
        // Do something different
        bar(nth_value_of<i>(args));
    }
}

Or for a more expressive idiom like this one:

template<typename... Ts>
void foo(Ts&&... args)
{
    static for_each (auto&& x : args)
    {
        foo(x);
    }
}

RELATED WORK:

I did some search on the Web and found out that something does indeed exist:

  • This link describes how to convert a parameter pack into a Boost.MPL vector, but that only goes half the way (if not less) towards the goal;
  • this question on SO seems to call for a similar and slightly related meta-programming feature (splitting an argument pack into two halves) - actually, there are several questions on SO which seem to be related to this issue, but none of the answer I have read solves it satisfactorily IMHO;
  • Boost.Fusion defines algorithms for converting an argument pack into a tuple, but I would prefer:
    1. not to create unnecessary temporaries to hold arguments that can (and should be) perfectly forwarded to some generic algorithms;
    2. have a small, self-contained library to do that, while Boost.Fusion is likely to include way more stuff than is needed to address this issue.

QUESTION:

Is there a relatively simple way, possibly through some template meta-programming, to achieve what I am looking for without incurring in the limitations of the existing approaches?

Intoxication answered 10/1, 2013 at 15:19 Comment(16)
If foo returned something, you could just write eat(foo(args)...) where eat is a function that does nothing with its arguments. Needs some tweaking for functions that return void, or if you want to specify the execution order (it has been discussed on usenet, probably comp.lang.c++.moderated, though I can't find it at the moment). It was discussed to allow foo(args);...Edbert
@MarcGlisse: You have a point, and indeed I tried that. The problem with that solution is that C++ does not guarantee any order for the evaluation of function arguments, which is something which is often desired when iterating (apart from the fact that functions would have to return a value even when they are not required to, but that's minor).Intoxication
I think there is a variant where the order of evaluation of the arguments is specified, maybe inside {} (initializer list) they are. As for the return type, you can probably do (foo(args),0)... or some other trick.Edbert
@MarcGlisse: it might be the case, please feel free to try it out and improve my library if you wish. i'd be glad if it could be made better. frankly I think my solution does not introduce any overhead nor does it place any constraint on the client side, which is fine for my purposes. but that doesn't mean it's perfect, of courseIntoxication
@MarcGlisse: To execute the function call in order, one could use this : auto eat[] = {foo(args)...};. then the order is guaranteed to be left to right.Brose
@Nawaz: wouldn't that allocate an array containing the results of each foo(arg)? also, what if the return types of foo() are different for different types of arg?Intoxication
@Nawaz: thanks for the confirmation. AndyProwl: a library solution is fine, I was just looking at the cases where we can do without, but in a large project I would definitely use a library.Edbert
@AndyProwl: If the return type is different for each overload of foo, then this should be fine : std::tuple<Args...> eat {foo(args)...};.Brose
@Nawaz: I see, but this way I would create a temporary tuple, which I wanted to avoid as it generates unnecessary copies/movesIntoxication
@AndyProwl: I don't know but the compilers might optimize that step (memory-wise), if not now, in future versions, because I suspect such patterns will show up frequently.Brose
@Nawaz: I see. well, if that was the case and the conversion from pack to tuple would be absolutely inexpensive in terms of memory and computation, then it might make sense to rework the algorithms of my library to work on tuples rather than argument packs, cause the concept extends to tuples quite straightforwardly. but then again Boost.Fusion probably does a better job than i could ever do when it comes to tuplesIntoxication
FWIW, in the past I'd already verified that both GCC and VC++ completely optimize away tuples of references in release builds – identical codegen to not using tuples (testing with Boost.Fusion).Schelling
@ildjarn: that's a useful information. it makes my work way less useful, but still a useful information. thank youIntoxication
Note: in python, there is a function that iterates over an iteratee and returns both the element with its index, it's called enumerate. I would consider changing the name from for_each which (in the standard library) is index-free, to something else... like enumerate and have the functor accept both element and index (and maybe total size as well ?) :)Outfoot
I was reading again your question (And your answer) and I have noticed that your requeriments could be achieved with my compile-time computing library at compile-time. Its not exactly the same, but its the same concept :)Instinct
This is the problem that Hana was designed to solve: "Boost.Hana is a library of combinators tailored towards the manipulation of heterogeneous collections (think std::tuple). It provides high level operations to manipulate those collections in compile-time efficient ways and with a great level of expressiveness. One of its main purpose is to unify type-level (think Boost.MPL) and heterogeneous value-level programming (think Boost.Fusion) under a single consistent interface, allowing the compile-time and the runtime worlds to interact in new, useful ways."Unrestrained
I
63

Since I was not happy with what I found, I tried to work out a solution myself and ended up writing a small library which allows formulating generic operations on argument packs. My solution has the following features:

  • Allows iterating over all or some elements of an argument pack, possibly specified by computing their indices on the pack;
  • Allows forwarding computed portions of an argument pack to variadic functors;
  • Only requires including one relatively short header file;
  • Makes extensive use of perfect forwarding to allow for heavy inlining and avoids unnecessary copies/moves to allow for minimum performance loss;
  • The internal implementation of the iterating algorithms relies on Empty Base Class Optimization for minimizing memory consumption;
  • It is easy (relatively, considering it's template meta-programming) to extend and adapt.

I will first show what can be done with the library, then post its implementation.

USE CASES

Here is an example of how the for_each_in_arg_pack() function can be used to iterate through all the arguments of a pack and pass each argument in input to some client-supplied functor (of course, the functor must have a generic call operator if the argument pack contains values of heterogenous types):

// Simple functor with a generic call operator that prints its input. This is used by the
// following functors and by some demonstrative test cases in the main() routine.
struct print
{
    template<typename T>
    void operator () (T&& t)
    {
        cout << t << endl;
    }
};

// This shows how a for_each_*** helper can be used inside a variadic template function
template<typename... Ts>
void print_all(Ts&&... args)
{
    for_each_in_arg_pack(print(), forward<Ts>(args)...);
}

The print functor above can also be used in more complex computations. In particular, here is how one would iterate on a subset (in this case, a sub-range) of the arguments in a pack:

// Shows how to select portions of an argument pack and 
// invoke a functor for each of the selected elements
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<0, halfSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );

    cout << "Printing second half:" << endl;
    for_each_in_arg_pack_subset(
        print(), // The functor to invoke for each element
        index_range<halfSize, packSize>(), // The indices to select
        forward<Ts>(args)... // The argument pack
        );
}

Sometimes, one may just want to forward a portion of an argument pack to some other variadic functor instead of iterating through its elements and pass each of them individually to a non-variadic functor. This is what the forward_subpack() algorithm allows doing:

// Functor with variadic call operator that shows the usage of for_each_*** 
// to print all the arguments of a heterogeneous pack
struct my_func
{
    template<typename... Ts>
    void operator ()(Ts&&... args)
    {
        print_all(forward<Ts>(args)...);
    }
};

// Shows how to forward only a portion of an argument pack 
// to another variadic functor
template<typename... Ts>
void split_and_print(Ts&&... args)
{
    constexpr size_t packSize = sizeof...(args);
    constexpr size_t halfSize = packSize / 2;

    cout << "Printing first half:" << endl;
    forward_subpack(my_func(), index_range<0, halfSize>(), forward<Ts>(args)...);

    cout << "Printing second half:" << endl;
    forward_subpack(my_func(), index_range<halfSize, packSize>(), forward<Ts>(args)...);
}

For more specific tasks, it is of course possible to retrieve specific arguments in a pack by indexing them. This is what the nth_value_of() function allows doing, together with its helpers first_value_of() and last_value_of():

// Shows that arguments in a pack can be indexed
template<unsigned I, typename... Ts>
void print_first_last_and_indexed(Ts&&... args)
{
    cout << "First argument: " << first_value_of(forward<Ts>(args)...) << endl;
    cout << "Last argument: " << last_value_of(forward<Ts>(args)...) << endl;
    cout << "Argument #" << I << ": " << nth_value_of<I>(forward<Ts>(args)...) << endl;
}

If the argument pack is homogeneous on the other hand (i.e. all arguments have the same type), a formulation such as the one below might be preferable. The is_homogeneous_pack<> meta-function allows determining whether all the types in a parameter pack are homogeneous, and is mainly meant to be used in static_assert() statements:

// Shows the use of range-based for loops to iterate over a
// homogeneous argument pack
template<typename... Ts>
void print_all(Ts&&... args)
{
    static_assert(
        is_homogeneous_pack<Ts...>::value, 
        "Template parameter pack not homogeneous!"
        );

    for (auto&& x : { args... })
    {
        // Do something with x...
    }

    cout << endl;
}

Finally, since lambdas are just syntactic sugar for functors, they can be used as well in combination with the algorithms above; however, until generic lambdas will be supported by C++, this is only possible for homogeneous argument packs. The following example also shows the usage of the homogeneous-type<> meta-function, which returns the type of all arguments in a homogeneous pack:

 // ...
 static_assert(
     is_homogeneous_pack<Ts...>::value, 
     "Template parameter pack not homogeneous!"
     );
 using type = homogeneous_type<Ts...>::type;
 for_each_in_arg_pack([] (type const& x) { cout << x << endl; }, forward<Ts>(args)...);

This is basically what the library allows doing, but I believe it could even be extended to carry out more complex tasks.

IMPLEMENTATION

Now comes the implementation, which is a bit tricky in itself so I will rely on comments to explain the code and avoid making this post too long (perhaps it already is):

#include <type_traits>
#include <utility>

//===============================================================================
// META-FUNCTIONS FOR EXTRACTING THE n-th TYPE OF A PARAMETER PACK

// Declare primary template
template<int I, typename... Ts>
struct nth_type_of
{
};

// Base step
template<typename T, typename... Ts>
struct nth_type_of<0, T, Ts...>
{
    using type = T;
};

// Induction step
template<int I, typename T, typename... Ts>
struct nth_type_of<I, T, Ts...>
{
    using type = typename nth_type_of<I - 1, Ts...>::type;
};

// Helper meta-function for retrieving the first type in a parameter pack
template<typename... Ts>
struct first_type_of
{
    using type = typename nth_type_of<0, Ts...>::type;
};

// Helper meta-function for retrieving the last type in a parameter pack
template<typename... Ts>
struct last_type_of
{
    using type = typename nth_type_of<sizeof...(Ts) - 1, Ts...>::type;
};

//===============================================================================
// FUNCTIONS FOR EXTRACTING THE n-th VALUE OF AN ARGUMENT PACK

// Base step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I == 0), decltype(std::forward<T>(t))>::type
{
    return std::forward<T>(t);
}

// Induction step
template<int I, typename T, typename... Ts>
auto nth_value_of(T&& t, Ts&&... args) ->
    typename std::enable_if<(I > 0), decltype(
        std::forward<typename nth_type_of<I, T, Ts...>::type>(
            std::declval<typename nth_type_of<I, T, Ts...>::type>()
            )
        )>::type
{
    using return_type = typename nth_type_of<I, T, Ts...>::type;
    return std::forward<return_type>(nth_value_of<I - 1>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the first value of an argument pack
template<typename... Ts>
auto first_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename first_type_of<Ts...>::type>(
            std::declval<typename first_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename first_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<0>((std::forward<Ts>(args))...));
}

// Helper function for retrieving the last value of an argument pack
template<typename... Ts>
auto last_value_of(Ts&&... args) ->
    decltype(
        std::forward<typename last_type_of<Ts...>::type>(
            std::declval<typename last_type_of<Ts...>::type>()
            )
        )
{
    using return_type = typename last_type_of<Ts...>::type;
    return std::forward<return_type>(nth_value_of<sizeof...(Ts) - 1>((std::forward<Ts>(args))...));
}

//===============================================================================
// METAFUNCTION FOR COMPUTING THE UNDERLYING TYPE OF HOMOGENEOUS PARAMETER PACKS

// Used as the underlying type of non-homogeneous parameter packs
struct null_type
{
};

// Declare primary template
template<typename... Ts>
struct homogeneous_type;

// Base step
template<typename T>
struct homogeneous_type<T>
{
    using type = T;
    static const bool isHomogeneous = true;
};

// Induction step
template<typename T, typename... Ts>
struct homogeneous_type<T, Ts...>
{
    // The underlying type of the tail of the parameter pack
    using type_of_remaining_parameters = typename homogeneous_type<Ts...>::type;

    // True if each parameter in the pack has the same type
    static const bool isHomogeneous = std::is_same<T, type_of_remaining_parameters>::value;

    // If isHomogeneous is "false", the underlying type is the fictitious null_type
    using type = typename std::conditional<isHomogeneous, T, null_type>::type;
};

// Meta-function to determine if a parameter pack is homogeneous
template<typename... Ts>
struct is_homogeneous_pack
{
    static const bool value = homogeneous_type<Ts...>::isHomogeneous;
};

//===============================================================================
// META-FUNCTIONS FOR CREATING INDEX LISTS

// The structure that encapsulates index lists
template <unsigned... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder;

    // Base step
    template <unsigned MIN, unsigned... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <unsigned MIN, unsigned N, unsigned... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<unsigned MIN, unsigned MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;

//===============================================================================
// CLASSES AND FUNCTIONS FOR REALIZING LOOPS ON ARGUMENT PACKS

// Implementation inspired by @jogojapan's answer to this question:
// https://mcmap.net/q/266616/-return-several-arguments-for-another-function-by-a-single-function

// Collects internal details for implementing functor invocation
namespace detail
{
    // Functor invocation is realized through variadic inheritance.
    // The constructor of each base class invokes an input functor.
    // An functor invoker for an argument pack has one base class
    // for each argument in the pack

    // Realizes the invocation of the functor for one parameter
    template<unsigned I, typename T>
    struct invoker_base
    {
        template<typename F, typename U>
        invoker_base(F&& f, U&& u) { f(u); }
    };

    // Necessary because a class cannot inherit the same class twice
    template<unsigned I, typename T>
    struct indexed_type
    {
        static const unsigned int index = I;
        using type = T;
    };

    // The functor invoker: inherits from a list of base classes.
    // The constructor of each of these classes invokes the input
    // functor with one of the arguments in the pack.
    template<typename... Ts>
    struct invoker : public invoker_base<Ts::index, typename Ts::type>...
    {
        template<typename F, typename... Us>
        invoker(F&& f, Us&&... args)
            :
            invoker_base<Ts::index, typename Ts::type>(std::forward<F>(f), std::forward<Us>(args))...
        {
        }
    };
}

// The functor provided in the first argument is invoked for each
// argument in the pack whose index is contained in the index list
// specified in the second argument
template<typename F, unsigned... Is, typename... Ts>
void for_each_in_arg_pack_subset(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    // Constructors of invoker's sub-objects will invoke the functor.
    // Note that argument types must be paired with numbers because the
    // implementation is based on inheritance, and one class cannot
    // inherit the same base class twice.
    detail::invoker<detail::indexed_type<Is, typename nth_type_of<Is, Ts...>::type>...> invoker(
        f,
        (nth_value_of<Is>(std::forward<Ts>(args)...))...
        );
}

// The functor provided in the first argument is invoked for each
// argument in the pack
template<typename F, typename... Ts>
void for_each_in_arg_pack(F&& f, Ts&&... args)
{
    for_each_in_arg_pack_subset(f, index_range<0, sizeof...(Ts)>(), std::forward<Ts>(args)...);
}

// The functor provided in the first argument is given in input the
// arguments in whose index is contained in the index list specified
// as the second argument.
template<typename F, unsigned... Is, typename... Ts>
void forward_subpack(F&& f, index_list<Is...> const& i, Ts&&... args)
{
    f((nth_value_of<Is>(std::forward<Ts>(args)...))...);
}

// The functor provided in the first argument is given in input all the
// arguments in the pack.
template<typename F, typename... Ts>
void forward_pack(F&& f, Ts&&... args)
{
    f(std::forward<Ts>(args)...);
}

CONCLUSION

Of course, even though I provided my own answer to this question (and actually because of this fact), I am curious to hear if alternative or better solutions exist which I have missed - apart from the ones mentioned in the "Related Works" section of the question.

Intoxication answered 10/1, 2013 at 15:19 Comment(17)
This honestly deserves more upvotes than it has. I've liked the idea of static if and static for, as to me, they would make metaprogramming a lot easier to read, write, and understand, but I wouldn't mind at all if these were implemented in the standard library.Purehearted
@chris: Actually, most of this functionality can be implemented with Boost.Fusion by first converting the argument pack into a tuple (by the time I wrote this, I did not know the compiler would optimize the tuple away). But thank you for your appreciation :)Intoxication
Hmm, I didn't know Boost Fusion could do that. I honestly have very little experience with Boost all around (I only found out about it after being equipped with C++11), but the Fusion library, and quite a few others, have sparked my interest.Purehearted
I know that this answer was written three years ago, but I hope you could answer me: Why you not have written last_type_of with a O(1) implementation? Anybody writes a 1024 param function (i.e. I'm not talking about template-deph problems), but you could reduce compilation time. Thanks.Instinct
@Manu343726: That was just 6 months ago, not 3 years :D I did not care about complexity because it is all done at compile-time, so computational complexity is usually that is not a concern. I just wrote the simplest implementation that came to my mind :)Intoxication
Oh, its true :D. If you must write an efficient implementation, what would you implement? I usually do this using a specialization and passing the variadic pack through a typelist.Instinct
Hello! I was analyzing your code and I could not understand something concerning the base step of the nth_value_of: why can not I declare the return type of the function as typename std::enable_if<(I == 0),T>::type? Why do you need to use that forward there? Thank you for your help!Lissa
@Kolyunya: Because that would make the function's return type equal to T when the argument t is bound to an rvalue, while I want T&& to be returned - to enable perfect forwarding, no copy. For instance, if an rvalue of type int is passed as the first argument, T will not be deduced to be int&&, but just int. I use std::forward to return int&& when an rvalue is passed and int& when an lvalue is passed. I can't expand on lvalues, rvalues, and perfect forwarding in this short comment, but if the explanation is not sufficient, try reading about those topics first. Cheers :)Intoxication
Hello again! I was analyzing your code further and I could not understand something about the return type of the induction step of the nth_value_of. Why would you wrap the std::declval of the nth_type_of in a std::forward when std::declval returns nothing but rvalue reference. Isn't the forward wrapper excessive in this case? Also is it a good idea to replace the content of the decltype with a recursive call to the nth_value_of as in the body of the function? Thank you very much for your help!Lissa
@Kolyunya: std::declval does not always return an rvalue reference: due to reference collapsing, if the template argument is an lvalue reference type, it will return an lvalue (reference). That machinery is needed for perfect forwarding of return values - I don't recall exactly all the details, I wrote this thing about 1 year ago ;) Anyway, you can try to modify it, compile it, and see what happens :) CheersIntoxication
I used to think that declval returns only rvalue-references after reading cplusplus.com/reference/utility/declval But it comes out that this is not always true due to the en.cppreference.com/w/cpp/utility/declval Thank you for your help again!Lissa
@Kolyunya: Well, technically what's written on cplusplus.com is correct (beware though, that site was known in the past for having inaccurate content), because it is true that std::declval "Returns an rvalue reference to type T". However, if T is itself a reference type, then reference collapsing kicks in, so you have to take that into account ;)Intoxication
This is what i call AWESOME!!! You should submit it to boost. Call it boost::parameter_pack or something.Linked
@user3019690: Thank you, I'm glad you like it! Unfortunately for me (and fortunately for the world), there's already Boost.Fusion which allows doing pretty much all of this and more ;)Intoxication
@AndyProwl: What's the purpose of forward_pack()? What problem does it solve that std::forward() doesn't?Adventuresome
@einpoklum: This was long ago and I don't remember why I've added it. As I look at it now, it does seem unnecessary, but who knows :DIntoxication
@AndyProwl 1. in your example, is there a way to pass additional parameters to print? 2 followup, what if the type of the additional parameter is derived from T ?Puling
E
10

Let me post this code, based on the discussion:

#include <initializer_list>
#define EXPAND(EXPR) std::initializer_list<int>{((EXPR),0)...}

// Example of use:
#include <iostream>
#include <utility>

void print(int i){std::cout << "int: " << i << '\n';}
int print(double d){std::cout << "double: " << d << '\n';return 2;}

template<class...T> void f(T&&...args){
  EXPAND(print(std::forward<T>(args)));
}

int main(){
  f();
  f(1,2.,3);
}

I checked the generated code with g++ -std=c++11 -O1 and main only contains 3 calls to print, there is no trace of the expansion helpers.

Edbert answered 10/1, 2013 at 19:17 Comment(6)
I appreciate you took time to try and answer my question. For the moment I have one comment to your solution (doesn't mean it cannot be fixed): it does not use perfect forwarding. With int and doubles that doesn't matter much, but with UDTs it means it will generate copies and moves. And you can't change EXPAND(print(args)) into EXPAND(print(forward<T>(args))), because the macro preprocessor will shout something impolite.Intoxication
I just added forward<T> (thanks, I had forgotten to add it) and the macro preprocessor didn't complain at all...Edbert
you are right, for some reason I assumed the preprocessor did not like angular brackets. not sure why. so i guess this is valid alternative to my approach for the simpler cases where you want to iterate on all elements of a range. it also has the advantage that it allows the invoked function to be a function template and not just a functor, as my solution requires.Intoxication
I am going to accept my own answer because I believe it is the one that covers the more general spirit of the question (how to perform generic operations on argument packs). Your technique is very interesting, because it is compact and does not require functors, but it applies to the whole parameter list and does not allow to easily split a pack or select a sub-pack through which iteration shall be done. Definitely a +1 thoughIntoxication
Ok. Note that the 2 approaches can easily be combined. Forward once to make an index_range available, and then use EXPAND to execute whatever code involving both the object and its index (it doesn't mind expanding 2 packs at the same time).Edbert
Absolutely: in fact, I integrated your macro definition in my library. Thanks a lot for contributing.Intoxication
O
5

Using an enumerate solution (ala Python).

Usage:

void fun(int i, size_t index, size_t size) {
    if (index != 0) {
        std::cout << ", ";
    }

    std::cout << i;

    if (index == size - 1) {
        std::cout << "\n";
    }
} // fun

enumerate(fun, 2, 3, 4);

// Expected output: "2, 3, 4\n"
// check it at: http://liveworkspace.org/code/1cydbw$4

Code:

// Fun: expects a callable of 3 parameters: Arg, size_t, size_t
// Arg: forwarded argument
// size_t: index of current argument
// size_t: number of arguments
template <typename Fun, typename... Args, size_t... Is>
void enumerate_impl(Fun&& fun, index_list<Is...>, Args&&... args) {
    std::initializer_list<int> _{
        (fun(std::forward<Args>(args), Is, sizeof...(Is)), 0)...
    };
    (void)_; // placate compiler, only the side-effects interest us
}

template <typename Fun, typename... Args>
void enumerate(Fun&& fun, Args&&... args) {
    enumerate_impl(fun,
                   index_range<0, sizeof...(args)>(),
                   std::forward<Args>(args)...);
}

The range builder (pilferred from your solution):

// The structure that encapsulates index lists
template <size_t... Is>
struct index_list
{
};

// Collects internal details for generating index ranges [MIN, MAX)
namespace detail
{
    // Declare primary template for index range builder
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder;

    // Base step
    template <size_t MIN, size_t... Is>
    struct range_builder<MIN, MIN, Is...>
    {
        typedef index_list<Is...> type;
    };

    // Induction step
    template <size_t MIN, size_t N, size_t... Is>
    struct range_builder : public range_builder<MIN, N - 1, N - 1, Is...>
    {
    };
}

// Meta-function that returns a [MIN, MAX) index range
template<size_t MIN, size_t MAX>
using index_range = typename detail::range_builder<MIN, MAX>::type;
Outfoot answered 13/2, 2013 at 16:56 Comment(3)
@AndyProwl: I must admit that in the last few years I have been surprised more than one time at the elegance of a number of Python "standard" functions. The enumerate example is something I really miss, though I suppose normally it would be more for (auto p: enumerate(container)) and I am not sure the two versions (container iteration and tuple iteration) would live well together :)Outfoot
I must shamefully confess my vaste ignorance of Python here :-) Well, it seems I should start studying itIntoxication
@AndyProwl: the function enumerate and the module itertools are good starting points :)Outfoot
G
0

The ... notation does have some interesting options, like:

template<typename T>
int print(const T& x) {
  std::cout << "<" << x << ">";
  return 0;
}

void pass(...) {}

template<typename... TS>
void printall(TS... ts){
  pass(print(ts)...);
}

Unfortunately, I don't know of any way to enforce the order in which the print functions are called (reverse, on my compiler). Note that print needs to return something.

This trick can be useful if you don't care about order.

Gratifying answered 15/1, 2013 at 6:46 Comment(1)
See gitorious.org/redistd/redistd/blobs/master/include/redi/… for one way to enforce the ordering, using the standard functional programming trick of printing the head of the parameter pack, then recursively processing the tailExon
A
0

After reading a few other posts and tinkering for a while I came up with the following (somewhat similar to the above, but the implementation is a little different). I wrote this using the Visual Studio 2013 compiler.

Usage using a lambda expression -

static_for_each()(
    [](std::string const& str)
    {
        std::cout << str << std::endl;
    }, "Hello, ", "Lambda!");

The downside when using a lambda is the parameters must be of the same type declared in the lambda's parameter list. This means it will only work with one type. If you want to use a templated function, you can use the next example.

Usage using struct wrapper functor -

struct print_wrapper
{
    template <typename T>
    void operator()(T&& str)
    {
        std::cout << str << " ";
    }
};

// 
// A little test object we can use.
struct test_object
{
    test_object() : str("I'm a test object!") {}
    std::string str;
};

std::ostream& operator<<(std::ostream& os, test_object t)
{
    os << t.str;
    return os;
}

//
// prints: "Hello, Functor! 1 2 I'm a test object!"
static_for_each()(print_wrapper(), "Hello,", "Functor!", 1, 2.0f, test_object());

This allows you to pass in any types you'd like and operate on them using the functor. I found this pretty clean and works nicely for what I wanted. You can also use it with a function parameter pack like this -

template <typename T, typename... Args>
void call(T f, Args... args)
{
    static_for_each()(f, args...);
}

call(print_wrapper(), "Hello", "Call", "Wrapper!");

Here is the implementation -

// 
// Statically iterate over a parameter pack 
// and call a functor passing each argument.
struct static_for_each
{
private:
    // 
    // Get the parameter pack argument at index i.
    template <size_t i, typename... Args>
    static auto get_arg(Args&&... as) 
    -> decltype(std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...)))
    {
        return std::get<i>(std::forward_as_tuple(std::forward<Args>(as)...));
    }

    //
    // Recursive template for iterating over 
    // parameter pack and calling the functor.
    template <size_t Start, size_t End>
    struct internal_static_for
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args)
        {
            f(get_arg<Start>(args...));
            internal_static_for<Start + 1, End>()(f, args...);
        }
    };

    //
    // Specialize the template to end the recursion.
    template <size_t End>
    struct internal_static_for<End, End>
    {
        template <typename Functor, typename... Ts>
        void operator()(Functor f, Ts&&... args){}
    };

public:
    // 
    // Publically exposed operator()(). 
    // Handles template recursion over parameter pack.
    // Takes the functor to be executed and a parameter 
    // pack of arguments to pass to the functor, one at a time.
    template<typename Functor, typename... Ts>
    void operator()(Functor f, Ts&&... args)
    {
        // 
        // Statically iterate over parameter
        // pack from the first argument to the
        // last, calling functor f with each 
        // argument in the parameter pack.
        internal_static_for<0u, sizeof...(Ts)>()(f, args...);
    }
};

Hope people find this useful :-)

Aorangi answered 7/11, 2014 at 21:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.