1
votes

Fonction de modèle C ++ - types multiples, arguments par défaut et ...?

J'ai quelques fonctions de chaudière que je voudrais remplacer par un modèle. Ils ressemblent à peu près à:

//... 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);

Les éléments qui nécessitent une abstraction sont le type de retour (uniquement le type contenu, toujours vecteur convient) les arguments après N code> (par exemple, inférieur et supérieur dans ce cas) et la distribution (par exemple, std :: uniform_real_distribution ).

Ce que j'aimerais en gros pouvoir écrire:

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

J'ai quelques sous-pièces

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

mais quand j'essaye d'assembler comme, par exemple

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
>

je rencontre des erreurs de compilation (dans ce cas error: expected unqualified-id ). Ce que je voudrais est-il à peu près réalisable? Cette approche va-t-elle dans la bonne direction ou dois-je faire quelque chose de fondamentalement différent? Si c'est dans le bon sens, que me manque-t-il?

EDIT:

Pour les contraintes applicatives: j'aimerais pouvoir déclarer les générateurs par défaut pour les arguments, mais J'ai besoin de les utiliser occasionnellement sans les valeurs par défaut. Ne pas avoir de valeurs par défaut est tout simplement gênant, mais pas fatal. Exemple:

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


13 commentaires

Pas la réponse, mais je suggère de ne pas stocker le résultat de std :: bind dans un std :: function . Si vous utilisez à la place auto , vous évitez la surcharge de l'effacement de type que std :: function ajoute.


"& args ..." n'est pas la façon dont vous développez un pack de paramètres, dans ce cas. Essayez std :: forward (args) ... . Et les paramètres du modèle doivent être Args ... && args . Il peut y avoir d'autres problèmes ici, mais c'est le premier problème évident.


@HolyBlackCat oui, j'ai appris cela plus tôt en essayant de construire certaines des autres pièces.


De plus, il doit s'agir de class ... Args dans la liste des modèles, pas seulement de Args ... .


"comme par exemple"? Pourquoi ne publiez-vous pas le code exact que vous avez essayé à la place afin que nous n'obtenions pas d'erreurs idiotes "" Args "n'a pas été déclaré"?


excuses concernant la partie Args - avait corrigé cela dans la version précédente de Q, devait se perdre dans les modifications.


@SamVarshavchik Je pense que c'est Args && ... args .


Paramètres de modèle de modèle


La partie args par défaut n'est pas claire. Vous pouvez utiliser des paramètres de modèle pour spécifier un type ou une valeur intégrale à la compilation. Il semble que vous essayez de faire les deux à la fois ici (et avec des doubles), ce qui ne fonctionnera pas. Vous pouvez spécifier tous les types d’arguments pour la distribution. Avec un peu de travail supplémentaire, vous pourrez peut-être également inclure des arguments par défaut pour les valeurs intégrales, mais vous ne pouvez pas vraiment communiquer des valeurs à virgule flottante avec des modèles.


Comment attribuer un alias à un nom de la fonction en C ++? - Débordement de pile


Voulez-vous que la partie args des paramètres du modèle soit transmise comme entrée de fonction?


Le type d'éléments de args est-il le même que celui de NUMERIC ?


@ user202729 pas nécessairement - ce sont tout ce que les fonctions de distribution utilisent comme paramètres. Concernant les paramètres de modèle vs args, je ne suis pas sûr de bien comprendre q - J'utilise finalement les fonctions telles que auto xmeans = generate_means (rng, 100, 0, 1), ymeans = generate_means (rng, 100, 0, 1 ); etc. Je voudrais pouvoir appeler generate_means (rng, 100) avec les valeurs par défaut définies lors de l'instanciation de la fonction à partir du modèle, mais permettant toujours une utilisation occasionnelle avec des valeurs non par défaut.


3 Réponses :


0
votes

Merci aux différents commentateurs, cela fonctionne maintenant (une fois ajouté aux blocs de travail de 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>;

Heureux de voir d'autres réponses, cependant - essayant toujours de comprendre la syntaxe des modèles C ++ en général, et préférerait une version qui me permette de définir des arguments par défaut.


0 commentaires

2
votes

Il est impossible d'avoir un nombre à virgule flottante comme paramètre de modèle. Cependant, vous pouvez faire quelque chose comme suit:

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

EDIT: Utilisez generate_n au lieu de generate pour des raisons de performances

EDIT2: Si vous souhaitez utiliser les paramètres par défaut comme vous l'avez fait pour x, y et z, vous pouvez également faire quelque chose comme ça:

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


0 commentaires

0
votes

Je serais tenté d'accepter simplement un objet de distribution construit.

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 commentaires