11

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!

s-v
  • 153
  • 1
  • 7

5 Answers5

8

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.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • 2
    `You could pick a random number ... at the start, and then ... selects the nth argument` -> that would be my preference: clear semantics, easy to get right. I'd take the extra code for it – sehe Nov 09 '11 at 23:45
  • @sehe: oh, OK then. Will add. – Steve Jessop Nov 09 '11 at 23:55
  • I was wrong anyway, it isn't really more code. And I should have used perfect forwarding like deft_code, but I'm not really up to speed on this. – Steve Jessop Nov 10 '11 at 00:05
  • I'd +1 again if I could :) -- is it me or are you missing the `select` overload for the `Only` case? – sehe Nov 10 '11 at 00:31
  • @sehe: Oh, yes, otherwise I have a call that will never be made, to a non-existent `select(size_t)`. Thanks. – Steve Jessop Nov 10 '11 at 00:55
  • 1
    @Steve: would you mind taking Arak's example and using the new `` header ? For C++11 code I think it's a good habit to get into, especially as the `uniform_int_distribution<> range(0, container.size()-1);` is definitely easier to get right. – Matthieu M. Nov 10 '11 at 07:30
  • @Matthieu: since the answer is accepted, it's worth it, by all means edit. I don't think the question is really about how best to generate the random number in C++11, but it might as well be right. – Steve Jessop Nov 10 '11 at 09:44
  • @Steve: okay, I edited taking Arak's code as an example. I don't understand where the state is stored though (either in the engine or the distribution), though I would have bet for the engine... so could not make any of them `const`. – Matthieu M. Nov 10 '11 at 10:15
8

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;
}
Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234
3

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

Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • Ah noticed you made the array non-static ... was just about to comment about that but seems like this works correctly now. – s-v Nov 09 '11 at 23:20
  • @s-v yeah I was thinking about that and thought it might mess up because of the static array, but I fixed it. – Seth Carnegie Nov 09 '11 at 23:21
  • Good stuff, I did not know that you could initialize an array with a parameter pack. – s-v Nov 09 '11 at 23:22
  • The problem is that this unnecessarily copies each element into the array. If a tuple of references were used instead, this needless overhead could be avoided. – ildjarn Nov 09 '11 at 23:26
  • @ildjarn I don't even know what a tuple is, you should write an answer. – Seth Carnegie Nov 09 '11 at 23:28
  • @SethCarnegie : I'm intimately familiar with tuples, but not so much with variadic templates' syntax, so I'm not really able to answer. ;-[ – ildjarn Nov 09 '11 at 23:30
2

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)... );
}
deft_code
  • 57,255
  • 29
  • 141
  • 224
  • Note that you still return a value by copy, even if what was passed in is an lvalue reference. _Almost_ perfect forwarding... ;-] – ildjarn Nov 10 '11 at 00:34
  • @ildjarn: I just tried it with `std::unique_ptr` and it worked fine. I don't think any copies are being made. – deft_code Nov 10 '11 at 01:18
  • That's because A) you passed in the `unique_ptr`s as rvalues so reference collapsing made the input types non-references altogether and B) `return` is allowed by the spec to treat the return value as an rvalue, making the return value move implicitly if it is a movable type. So yes, return values will be moved when possible if an rvalue is passed in, but that's missing the point. :-] The point is, if an lvalue is passed in, an lvalue reference should be returned, not a copied/moved instance of said lvalue. I.e, [this](http://ideone.com/tKl7j) works but [this](http://ideone.com/d1EwJ) doesn't. – ildjarn Nov 10 '11 at 01:25
  • @ildjarn: are you being pedantic or are you saying that I've done something wrong in the way I implemented the forwarding. Your second example can never work as it requires a copy be made (`i` would be a copy of `a`, `b`, or `c`). The definition of perfect forwarding gets fuzzy when return values get involved, but I think I've done it right. Imagine a macro version of random_picker, that only accepts two parameters and uses the ternary operator. That's as close to perfect forwarding as we're going to get. My variadic version of `random_picker` has the same semantics. – deft_code Nov 10 '11 at 02:16
0

The usual way to pick a random element of a linked list is by giving a 1/1, 1/2, 1/3, ..., 1/n chance to replacing the a picked element with the current element at each link.

#include <cstdlib>
#include <ctime>
#include <iostream>

namespace {
template<typename T>
T randomPickerHelper(int& sz, T first) {
    return first;
}
template<typename T, typename ...Args>
T randomPickerHelper(int& sz, T first, Args ...rest) {
    T next = randomPickerHelper(sz, rest...);
    return std::rand() % ++sz ? next : first;
}
}

template<typename T, typename... Args>
T randomPicker(T first, Args ...rest) {
    int sz = 1;
    return randomPickerHelper(sz, first, rest...);
}

int main() {
    std::srand(std::time(0));
    for (int i = 0; i < 1000000; ++i) {
        std::cout << randomPicker(1, 2, 3, 4, 5) << std::endl;
    }
    return 0;
}

Calls rand() a lot of times, though.


template<typename T, typename ...Args>
struct Count {
    static const int sz = Count<Args...>::sz + 1;
};
template<typename T>
struct Count<T> {
    static const int sz = 1;
};

template<typename T>
T pick(int n, T first) {
    return first;
}

template<int N, typename T, typename ...Args>
T pick(int n, T first, Args ...rest) {
    if (n == Count<T, Args...>::sz) {
        return first;
    }
    return pick(n, rest...);
}

template<typename T, typename ...Args>
T randomPicker(T first, Args ...rest) {
    return pick(std::rand() % Count<T, Args...>::sz + 1, first, rest...);
}

I feel like this should be possible, but GCC 4.6.0 doesn't support expanding <Args...>.

ephemient
  • 198,619
  • 38
  • 280
  • 391