Reputation: 523
I'm writing a function which I want to accept a distribution as a parameter. Let's say the following:
#include<random>
#include<iostream>
using namespace std;
random_device rd;
mt19937 gen(rd());
void print_random(uniform_real_distribution<>& d) {
cout << d(gen);
}
Now is there a way to generalise this code, in a short way, in C++ such that it would accept all distributions and distributions only (otherwise the compiler should complain)? Edit: To clarify, the solution should also be able to accept only a subset of all distributions (which would have to be pre-specified).
I would for example accept the ability to define a type as a collection of allowed types but it would be even better if there is already a type which has this property for distributions.
Upvotes: 16
Views: 541
Reputation: 275760
First, some boilerplate to give us a SFINAE friendly invoke type test:
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;
now invoke< Foo(int, int) >
is the type you get when you take a variable of type Foo
and invoke it with two int
s, and it evaluates in a SFINAE friendly manner.
This is basically a SFINAE friendly std::result_of
.
Next, some more pretty stuff. result_type
and param_type
save on typing elsewhere:
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 >
will take a template X
and apply T
. If this succeeds, it is true_type
, otherwise 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 {};
}
This gives us has_result_type
etc in a pretty way:
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) >;
I think the simplicity of these declarations is worth the earlier boilerplate.
Now, a bit of boolean metaprogramming:
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>{}... >;
And we get our one line pretty is_distribution
:
template<class T>
using is_distribution = passes_tests< T, has_result_type, has_param_type, can_twist_invoke >;
This does not yet cover .param
or .reset
.
This style leads to more code, but the "nasty" stuff gets hidden away in details namespaces. Someone who sees is_distribution
can look at the definition and see self-documented what it means. Only after drilling down will they see the messier implementation details.
Upvotes: 0
Reputation: 55897
There is no such traits in standard library. You can just write something like
template<typename T>
struct is_distribution : public std::false_type {};
and specialize for each type, that is distribution
template<typename T>
struct is_distribution<std::uniform_int_distribution<T> > :
public std::true_type {};
Then just
template<typename Distr>
typename std::enable_if<is_distribution<Distr>::value>::type
print_random(Distr& d)
{
cout << d(gen);
}
Also, you can use something like concepts-lite (but with decltypes, since there is no this feature now), it can not work in some cases. In standard there are rules, that should any distribution follow (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())
{
}
If you want just check that type is callable with some generator and execution of this call returns result_type you can just simplify function
template<typename D>
auto is_distribution(D& d) ->
decltype(std::is_same<typename D::result_type,
decltype(d(*static_cast<std::mt19937*>(0)))>::value);
all this things will be much simple, when concepts-lite will be available in standard.
Upvotes: 8
Reputation: 15334
I would just do:
template<typename Distribution>
void print_random(Distribution& d) {
cout << d(gen);
}
Anything that doesn't satisfy the implicit interface for a distribution will not compile. i.e it must have an operator()
that takes a generator as a parameter and returns a value.
Upvotes: 4