Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

random picker function using variadic templates -- is it possible?

I would like to use C++11's variadic templates to achieve a generalized "random picker" function.

Something like this...

template <typename T>
T randomPicker(T one, T two, T three)
{
    int pick = 3 * (rand() / double(RAND_MAX));
    switch (pick)
    {
        case 0:
            return one;
        case 1:
            return two;
        default:
            return three;
    }
}

... except generalized to accept any number of parameters (each of the same type, as above -- although accepting any type as a parameter and converting the chosen one to some specific type T upon return would be acceptable also).

I understand the idea of using template recursion to achieve things like the typesafe printf, etc. Can variadic templates also be used to create the sort of function described above? Any tips appreciated!

like image 627
s-v Avatar asked Nov 09 '11 23:11

s-v


4 Answers

Something like this, although I can't test it:

template <typename First, typename... Others>
First randompicker(First first, Others ...args) {
    const size_t len = sizeof...(args) + 1;
    if (rand() / double(RAND_MAX) < 1.0 / len) {
        return first;
    }
    return randompicker(args...);
}

template <typename Only>
Only randompicker(Only only) {
    return only;
}

I'm not sure whether the overload there is right -- presumably a parameter pack can be empty, I don't know whether I can still overload for one argument without ambiguity.

Admittedly this uses more random numbers than your example with 3 args, and may be more sensitive to bias from rounding errors. So, you could pick a random number from 0 to len-1 at the start, and then call a recursive function that selects the nth argument out of the parameter pack:

template <typename First, typename... Others>
First select(size_t idx, First first, Others ...args) {
    if (idx == 0) return first;
    return select(idx-1, args...);
}

template <typename Only>
Only select(size_t, Only only) {
    return only;
}

template <typename First, typename... Others>
First randompicker(First first, Others ...args) {
    static std::default_random_engine re;

    const size_t len = sizeof...(args) + 1;
    std::uniform_int_distribution<size_t> range{0, len - 1};

    const size_t idx = range(re);
    return select(idx, first, args...);
}

In all cases, I have n if/else statements instead of an n-way switch. You might be lucky with the optimizer, or you might be able to "unroll the loop" a bit by having First, Second ... A few parameter args before the variable args.

like image 144
Steve Jessop Avatar answered Nov 11 '22 22:11

Steve Jessop


I am not sure if you have to use variadic templates, but IMHO it is easier to go with initializer lists.

#include <cstddef>
#include <iostream>
#include <random>
#include <algorithm>
#include <initializer_list>
using namespace std;

template <class T>
T random_picker(initializer_list<T> container){
    static default_random_engine re;
    uniform_int_distribution<size_t> range{0, container.size()-1};
    auto random_iterator = container.begin();
    advance(random_iterator, range(re));
    return *random_iterator;
}

int main(){
    for(size_t i = 0; i < 10; ++i)
        cout << random_picker({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) << endl;
}
like image 37
Khaled Alshaya Avatar answered Nov 11 '22 21:11

Khaled Alshaya


One way is you could do something like this:

template<typename T, typename... Args>
T randomPicker(T first, Args ...rest) {
    T array[sizeof...(rest) + 1] = {first, rest...};

    return array[rand() % (sizeof...(rest) + 1)];
}

Tested on IdeOne

like image 23
Seth Carnegie Avatar answered Nov 11 '22 20:11

Seth Carnegie


This should work. randomPicker chooses which one of the parameters it will return. randomPicker_impl works through the paramters till the correct one is chosen. The overload for Last ensures the template expansion terminates.

Full working code here: ideone.com/2TEH1

template< typename Ret, typename Last >
Ret random_picker_impl( size_t i, Last&& last )
{
   return std::forward<Last>(last);
}

template< typename Ret, typename First, typename Second, typename ... Rest >
Ret random_picker_impl( size_t i, First&& first, Second&& second, Rest&&... rest )
{
   if( i == 0 )
   {
      return std::forward<First>(first);
   }
   else
   {
      return random_picker_impl<Ret>( i-1, std::forward<Second>(second), std::forward<Rest>(rest)... );
   }
}

template< typename First, typename ... Rest >
First random_picker( First&& first, Rest&&... rest )
{
   size_t index = (sizeof...(rest) + 1) * (std::rand() / double(RAND_MAX));
   return random_picker_impl<First>( index, std::forward<First>(first), std::forward<Rest>(rest)... );
}
like image 45
deft_code Avatar answered Nov 11 '22 21:11

deft_code