使用可变模板的随机选取器函数——这是可能的吗

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

本文关键字:函数 随机 选取      更新时间:2023-10-16

我想使用C++11的可变模板来实现一个通用的"随机选择器"函数。

像这样的。。。

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;
    }
}

除非被泛化为接受任何数量的参数(如上所述,每个参数都是相同的类型——尽管接受任何类型作为参数并在返回时将所选类型转换为某个特定类型T也是可以接受的(。

我理解使用模板递归来实现typesafe printf等功能的想法。可变模板也可以用于创建上述类型的函数吗?任何提示都将不胜感激!

类似的东西,尽管我不能测试它:

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;
}

我不确定那里的重载是否正确——大概一个参数包可以是空的,我不知道我是否仍然可以为一个参数重载而不产生歧义。

诚然,这比您的3个参数的示例使用了更多的随机数,并且可能对舍入误差的偏差更敏感。因此,您可以在开始时从0len-1中选择一个随机数,然后调用一个递归函数,从参数包中选择第n个参数:

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...);
}

在所有情况下,我都有nif/else语句,而不是n路开关。您可能很幸运有了优化器,或者您可以通过FirstSecond。。。变量args之前的A few参数args。

我不确定是否必须使用可变模板,但IMHO使用初始值设定项列表更容易。

#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;
}

一种方法是你可以做这样的事情:

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

在IdeOne 上测试

这应该可以工作。randomPicker选择它将返回哪一个参数。randomPicker_impl通过参数进行工作,直到选择正确的参数。Last的重载确保模板扩展终止。

完整的工作代码: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)... );
}

选择链表中随机元素的常用方法是给出1/1,1/2,1/3。。。,1/n在每个链路上用当前元素替换所拾取的元素的机会。

#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;
}

不过,多次调用rand()


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...);
}

我觉得这应该是可能的,但GCC 4.6.0不支持扩展<Args...>