Haffi112
Haffi112

Reputation: 523

C++, how to use a subset of all types

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

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Upvotes: 0

ForEveR
ForEveR

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

Chris Drew
Chris Drew

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

Related Questions