16

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.

Haffi112
  • 553
  • 5
  • 18
  • If you accept all distributions, you would have to ensure that the type is valid for the distribution, otherwise it's undefined behavior. –  Dec 15 '14 at 11:40
  • 1
    @remyabel What type? I thought all distributions can use a standard generator like `mt19937`. – Chris Drew Dec 15 '14 at 12:10
  • What is your definition of "distributions only". In my mind anything that satisfies the implicit interface of a distribution _is a_ distribution. – Chris Drew Dec 15 '14 at 12:12
  • @ChrisDrew I added that because I do not want to accept non-distribution types, if you accept all types you also accept distributions but that's not what I want. It would be fine if it accepts only types which satisfy the implicit interface of a distribution, e.g. something which accepts a random number generator and outputs a number. – Haffi112 Dec 15 '14 at 13:12

3 Answers3

8

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.

ForEveR
  • 55,233
  • 2
  • 119
  • 133
4

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.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • This solution is more concise but it does not solve the problem of defining a type as a subset of allowed types. – Haffi112 Dec 15 '14 at 13:21
  • 1
    Sorry, I must have misunderstood you question. You stated you want it to accept "all distributions". Do you want it to only accept _some_ distributions? – Chris Drew Dec 15 '14 at 13:28
  • The code above is just an example of a use case. And yes, if you only want to accept some distributions this solution would not cover it. – Haffi112 Dec 15 '14 at 13:40
  • Regarding accepting all distributions this code covers that case but it does not cover the "only distributions" case because it also compiles all types which accept a generator and return a value (and they are not necessarily distributions). – Haffi112 Dec 15 '14 at 13:47
  • 1
    @Haffi112 name one thing that takes a generator, returns a value, and is **not** a distribution. – Bartek Banachewicz Dec 15 '14 at 13:55
  • You could for example create a class which upon receiving a generator will output a string. This would not be desired. You could call this a distribution since it's just a constant string function but it would be preferred that the returned value is a number. In that case I would agree that it is a distribution but it is still more general than for example restricting oneself to the distributions available in . – Haffi112 Dec 18 '14 at 12:20
0

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 ints, 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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524