С++, как использовать подмножество всех типов

Я пишу функцию, которую хочу принять в качестве параметра. Скажем следующее:

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

random_device rd;
mt19937 gen(rd());

void print_random(uniform_real_distribution<>& d) {
    cout << d(gen);
}

Теперь есть способ обобщить этот код, вкратце, на С++, чтобы он принимал только все дистрибутивы и дистрибутивы (иначе компилятор должен жаловаться)? Изменить: Чтобы уточнить, решение должно также иметь возможность принимать только подмножество всех дистрибутивов (которые должны быть предварительно заданы).

Я бы, например, согласился с возможностью определять тип как набор разрешенных типов, но было бы еще лучше, если бы уже был тип, который имеет это свойство для распределений.

Ответы

Ответ 1

В стандартной библиотеке таких признаков нет. Вы можете просто написать что-то вроде

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);
}

Кроме того, вы можете использовать что-то вроде concept-lite (но с 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);

все это будет очень просто, когда concept-lite будет доступен по стандарту.

Ответ 2

Я бы просто сделал:

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

Все, что не удовлетворяет неявному интерфейсу для распространения, не будет компилироваться. т.е. он должен иметь operator(), который принимает генератор в качестве параметра и возвращает значение.

Ответ 3

Во-первых, некоторый шаблон, чтобы дать нам специальный тест типа 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;

now invoke< Foo(int, int) > - это тип, который вы получаете, когда вы берете переменную типа Foo и вызываете ее с двумя int s, и она оценивается дружественным образом 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.

Этот стиль приводит к большему количеству кода, но "неприятные" вещи скрываются в деталях пространств имен. Тот, кто видит is_distribution, может посмотреть на определение и увидеть самодокументированное, что это значит. Только после сверления они видят детали беспорядочной реализации.