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

C++,如何使用所有类型的子集,c++,random,distribution,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++中概括这个代码,这样

我正在写一个函数,我想接受一个分布作为参数。让我们这样说:

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

random_device rd;
mt19937 gen(rd());

void print_random(uniform_real_distribution<>& d) {
    cout << d(gen);
}
现在有没有办法在C++中概括这个代码,这样它就可以接受所有的分布和分布,而编译器只会抱怨呢?编辑:为了澄清,该解决方案还应该能够只接受必须预先指定的所有分发的子集


例如,我会接受将类型定义为允许的类型集合的能力,但如果已经有一个类型具有此属性用于分发版,那就更好了。

标准库中没有这样的特性。你可以这样写

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);
}
此外,您还可以使用类似于概念lite的东西,但使用decltypes,因为现在没有此功能,所以在某些情况下它无法工作。在标准中有一些规则,任何分发都应遵循n3376 26.5.1.6/表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())
{
}
如果您只想检查该类型是否可以使用某个生成器调用,并且该调用的执行将返回结果\ u类型,您可以简化该函数

template<typename D>
auto is_distribution(D& d) ->
decltype(std::is_same<typename D::result_type,
decltype(d(*static_cast<std::mt19937*>(0)))>::value);
当concepts lite在standard中可用时,所有这些事情都将非常简单。

我只想做:

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

任何不满足发行版隐式接口的内容都不会编译。i、 它必须有一个运算符,该运算符将生成器作为参数并返回一个值。

首先,一些样板文件为我们提供了一个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;
详细信息::has_属性将采用模板X并应用T。如果成功,则为true_类型,否则为false_类型:

我认为这些声明的简单性值得参考早期的样板文件

现在,有一点布尔元编程:

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>{}... >;
这还不包括.param或.reset


这种风格会产生更多的代码,但讨厌的东西会隐藏在名称空间的细节中。看到is_发行版的人可以查看定义并查看自我记录的含义。只有向下钻取之后,他们才会看到messier实现的详细信息。

如果接受所有发行版,则必须确保该类型对发行版有效,否则它是未定义的行为。@remyabel什么类型?我认为所有发行版都可以使用mt19937这样的标准生成器。您对发行版的定义是什么。在我看来,任何满足发行版隐式接口的东西都是发行版。@ChrisDrew我补充说,因为我不想接受非发行版类型,如果你接受所有类型,你也会接受发行版,但这不是我想要的。如果它只接受满足分布的隐式接口的类型就可以了,例如,接受随机数生成器并输出一个数字的东西。除了dgen:-好的,谢谢,这非常有用,但相当难看:我更喜欢一种更短/更清晰的方法来实现这一点。@Haffi112是的,C++并不是说简短明了。这个解决方案更简洁,但它不能解决将类型定义为允许类型的子集的问题。对不起,我一定误解了你的问题。您声明希望它接受所有分发。您希望它只接受一些发行版吗?上面的代码只是一个用例的示例。是的,如果您只想接受某些发行版,此解决方案将不包括在内。关于接受所有发行版,此代码涵盖了这种情况,但不包括唯一的发行版情况,因为它还编译所有接受生成器并返回值的类型,它们不一定是发行版。@Haffi112举一个例子接受生成器,返回值,而不是分布。
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 {};
}
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>{}... >;
template<class T>
using is_distribution = passes_tests< T, has_result_type, has_param_type, can_twist_invoke >;