С++, как использовать подмножество всех типов
Я пишу функцию, которую хочу принять в качестве параметра. Скажем следующее:
#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
, может посмотреть на определение и увидеть самодокументированное, что это значит. Только после сверления они видят детали беспорядочной реализации.