我正在编写一个函数,我想接受一个分布作为参数。让我们说以下内容:
#include<random>
#include<iostream>
using namespace std;
random_device rd;
mt19937 gen(rd());
void print_random(uniform_real_distribution<>& d) {
cout << d(gen);
}
现在有一种方法可以在C ++中概括这段代码,以简短的方式,这样它只接受所有的发行版和发行版(否则编译器应该抱怨)? 编辑:为了澄清,解决方案还应该只能接受所有发行版的子集(必须预先指定)。
我会接受将类型定义为允许类型的集合的能力,但如果已经存在具有此属性的类型,那么它会更好。
答案 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_type
和param_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
的人可以查看定义并查看自我记录的含义。只有在钻井后,他们才能看到更加混乱的实施细节。