C ++,如何使用所有类型的子集

时间:2014-12-15 11:25:36

标签: c++ random distribution

我正在编写一个函数,我想接受一个分布作为参数。让我们说以下内容:

#include<random>
#include<iostream>
using namespace std;

random_device rd;
mt19937 gen(rd());

void print_random(uniform_real_distribution<>& d) {
    cout << d(gen);
}

现在有一种方法可以在C ++中概括这段代码,以简短的方式,这样它只接受所有的发行版和发行版(否则编译器应该抱怨)? 编辑:为了澄清,解决方案还应该只能接受所有发行版的子集(必须预先指定)。

我会接受将类型定义为允许类型的集合的能力,但如果已经存在具有此属性的类型,那么它会更好。

3 个答案:

答案 0 :(得分:8)

标准库中没有这样的特征。你可以写一些像

这样的东西
template<typename T>
struct is_distribution : public std::false_type {};

并专门针对每种类型,即分发

template<typename T>
struct is_distribution<std::uniform_int_distribution<T> > :
public std::true_type {};

然后只是

template<typename Distr>
typename std::enable_if<is_distribution<Distr>::value>::type 
print_random(Distr& d)
{
    cout << d(gen);
}

此外,您可以使用类似概念的内容(但是使用decltypes,因为现在没有此功能),在某些情况下它无法使用。在标准中有规则,应遵循任何分布(n3376 26.5.1.6/Table 118)。

template<typename D>
constexpr auto is_distribution(D& d) ->
decltype(std::declval<typename D::result_type>(),
std::declval<typename D::param_type>(),
d.reset(), d.param(), d.param(std::declval<typename D::param_type>()), true);

template<typename D>
auto print_random(D& d) -> decltype(is_distribution(d), void())
{
}

如果你只想检查那个类型是否可以用某个生成器调用,并且执行此调用返回result_type你可以简化函数

template<typename D>
auto is_distribution(D& d) ->
decltype(std::is_same<typename D::result_type,
decltype(d(*static_cast<std::mt19937*>(0)))>::value);

所有这些事情都会非常简单,当概念 - 精简版将以标准方式提供时。

答案 1 :(得分:4)

我会这样做:

template<typename Distribution>
void print_random(Distribution& d) {
    cout << d(gen);
}

任何不满足分发的隐式接口的东西都不会编译。即它必须有operator(),它将生成器作为参数并返回一个值。

答案 2 :(得分:0)

首先,一些样板给我们一个SFINAE友好的调用类型测试:

namespace invoke_details {
  template<class Sig,class=void> struct invoke {};
  template<class F, class...Args> struct invoke<
    F(Args...),
    void( decltype( std::declval<F>(Args...) ) )
  > {
    using type=decltype( std::declval<F>(Args...) );
  };
}
template<class Sig> using invoke=typename invoke_details::invoke<Sig>::type;

现在invoke< Foo(int, int) >是您获取Foo类型的变量并使用两个int调用它时获得的类型,并且它以SFINAE友好的方式进行评估。

这基本上是SFINAE友好std::result_of

接下来,一些更漂亮的东西。 result_typeparam_type可以节省在其他地方输入内容:

template<class T>
using result_type = typename T::result_type;
template<class T>
using param_type = typename T::param_type;

details::has_property< X, T >将采用模板X并应用T。如果成功,则为true_type,否则为false_type

namespace details {
  template<template<class>class X, class T, class=void>
  struct has_property : std::false_type {};
  template<template<class>class X, class T>
  struct has_property<X,T,void(X<T>)> : std::true_type {};
}

这给了我们has_result_type等方式:

template<class T>
using has_result_type = details::has_property< result_type, T >;
template<class T>
using has_param_type = details::has_property< param_type, T >;
template<class Sig>
using can_invoke = details::has_property< invoke, Sig >;
template<class T>
using can_twist_invoke = can_invoke< T(std::mt19937) >;

我认为这些声明的简单性值得早期的样板。

现在,有点布尔元编程:

template<bool...> struct all_of : std::true_type {};
template<bool b0, bool... bs> struct all_of : std::integral_constant< bool, b0 && all_of<bs...>{} > {};

template<class T, template<class>class... Tests>
using passes_tests = all_of< Tests<T>{}... >;

我们的一行非常is_distribution

template<class T>
using is_distribution = passes_tests< T, has_result_type, has_param_type, can_twist_invoke >;

这还不包括.param.reset

这种风格会带来更多的代码,但是#34;讨厌&#34;东西被隐藏在细节名称空间中。看到is_distribution的人可以查看定义并查看自我记录的含义。只有在钻井后,他们才能看到更加混乱的实施细节。