Шаблонная функция C ++ - несколько типов, по умолчанию и ... аргументы? - PullRequest
0 голосов
/ 16 января 2019

У меня есть пара функций, которые я хотел бы заменить шаблоном. Они выглядят примерно так:

std::vector<double> generate_means(
  std::mt19937& g, unsigned int N,
  double lower = -1.0, double upper = 1.0
) {
  std::uniform_real_distribution<double> dist(lower, upper);
  std::function<double()> rng = std::bind(dist, g);
  std::vector<double> res(N);
  std::generate(std::begin(res), std::end(res), gen);
  return res;
}

Элементам, которые нуждаются в абстракции, являются возвращаемый тип (только содержащийся тип, всегда vector в порядке), аргументы после N (например, lower и upper в данном случае) и распределение (например, std::uniform_real_distribution).

Что бы я хотел примерно уметь написать:

auto generate_means = generate_template<
  double, // results vector<double>
  std::uniform_real_distribution, // uses uniform distro
  double=-1.0,double=1.0 // with default args
>
auto generate_norm_deviates = generate_template<
  double, // still provides vector<double>
  std::normal_distribution, // different distro
  double=0, double=1.0 // different defaults
>
auto generate_category_ids = generate_template<
  unsigned int,
  std::uniform_int_distribution,
  unsigned int=0, unsigned int // again with two args, but only one default
>

У меня есть несколько частей

template <class NUMERIC>
using generator = std::function<NUMERIC()>;

template <class NUMERIC>
std::vector<NUMERIC> series(unsigned int length, generator<NUMERIC> gen) {
  std::vector<NUMERIC> res(length);
  std::generate(std::begin(res), std::end(res), gen);
  return res;
};

но когда я пытаюсь собрать как, например

template <class NUMERIC, class DIST, class...Args>
std::vector<NUMERIC> generator_template(
  std::mt19937& g, unsigned int N,
  Args... args
) {
  DIST<NUMERIC> dist(&args...);
  generator<NUMERIC> gen = std::bind(dist, g);
  return series(N, gen);
} 

Я сталкиваюсь с ошибками компиляции (в данном случае error: expected unqualified-id). Что бы я хотел примерно достижимо? Это правильный подход или мне нужно сделать что-то принципиально другое? Если это в правильном направлении, что я пропускаю?

EDIT:

Для ограничений приложения: я хотел бы иметь возможность объявлять генераторы со значениями по умолчанию для аргументов, но мне иногда нужно использовать их без значений по умолчанию. Отсутствие значений по умолчанию просто неудобно, но не смертельно. Пример:

//... assorted calculations...
auto xmeans = generate_means(rng, 100); // x on (-1,1);
auto ymeans = generate_means(rng, 100); // y on (-1,1);
auto zmeans = generate_means(rng, 100, 0, 1); // z on (0,1);

Ответы [ 3 ]

0 голосов
/ 16 января 2019

Невозможно использовать число с плавающей запятой в качестве параметра шаблона. Однако вы можете сделать следующее:

#include <random>
#include <limits>
#include <algorithm>
#include <vector>
#include <iostream>

template<typename T, template<typename> typename Distribution>
auto generate_random_template(T min = std::numeric_limits<T>::lowest(),
                              T max = std::numeric_limits<T>::max()) {
    return [distribution = Distribution<double>{min, max}]
        (auto &&generator, std::size_t number) mutable {
        std::vector<T> result;
        result.reserve(number);
        auto generate = [&](){return distribution(generator);};
        std::generate_n(std::back_inserter(result), number, generate);
        return result;
    };
}

int main() {
    auto generate_means = generate_random_template<double, std::uniform_real_distribution>(0.0, 1.0);
    std::mt19937 g;
    std::vector<double> randoms = generate_means(g, 10);

    for(auto r : randoms) std::cout << r << std::endl;

    return 0;
}

РЕДАКТИРОВАТЬ: Используйте generate_n вместо generate по причинам исполнения

РЕДАКТИРОВАТЬ2: Если вы хотите использовать параметры по умолчанию, как вы сделали для x, y и z, вы также можете сделать что-то вроде этого:

#include <random>
#include <limits>
#include <algorithm>
#include <vector>
#include <iostream>

template<typename T, template<typename> typename Distribution>
auto generate_random_template(T min = std::numeric_limits<T>::lowest(),
                              T max = std::numeric_limits<T>::max()) {
    return [distribution = Distribution<double>{min, max}, min, max]
        (auto &&generator, std::size_t number, auto ...args) mutable {
        std::vector<T> result;
        result.reserve(number);

        if constexpr(sizeof...(args) > 0)
            distribution.param(typename Distribution<T>::param_type(args...));

        else
            distribution.param(typename Distribution<T>::param_type(min, max));

        auto generate = [&](){return distribution(generator);};
        std::generate_n(std::back_inserter(result), number, generate);
        return result;
    };
}

int main() {
    auto generate_means = generate_random_template<double, std::uniform_real_distribution>(-1.0, 1.0);
    std::mt19937 g;
    // x and y are between -1 and 1
    std::vector<double> x = generate_means(g, 10);
    std::vector<double> y = generate_means(g, 10);
    std::vector<double> z = generate_means(g, 10, 0.0, 1.0); // z is between 0 and 1

    for(int i = 0; i < 10; ++i) {
        std::cout << x[i] << "," << y[i] << "," << z[i] << std::endl;   
    }
    return 0;
}
0 голосов
/ 16 января 2019

Я хотел бы просто принять построенный объект распределения.

template <typename Dist, typename URBG>
std::vector<typename Dist::value_type> generate(Dist&& dist, URBG&& gen, std::size_t N)
{
    std::vector<typename Dist::value_type> res(N);
    std::generate(res.begin(), res.end(), std::bind(std::forward<Dist>(dist), std::forward<URBG>(gen)));
    return res;
}
0 голосов
/ 16 января 2019

Спасибо, разные комментаторы, теперь это работает (при добавлении в рабочие блоки из Q):

template <class NUMERIC, template<class> class DIST, class ... Args>
std::vector<NUMERIC> generator_template(
  std::mt19937& g, unsigned int N,
  Args &&... args
) {
  DIST<NUMERIC> dist(std::forward<Args>(args)...);
  generator<NUMERIC> gen = std::bind(dist, g);
  return series(N, gen);
};

auto generate_test = generator_template<double, std::uniform_real_distribution, double, double>;

Тем не менее, я рад видеть другие ответы - все еще пытаюсь понять синтаксис шаблона C ++ в целом и предпочел бы версию, которая позволяет мне задавать аргументы по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...