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; }
3 Réponses :
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.
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; }
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; }
Pas la réponse, mais je suggère de ne pas stocker le résultat de
std :: bind
dans unstd :: function
. Si vous utilisez à la placeauto
, vous évitez la surcharge de l'effacement de type questd :: 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 êtreArgs ... && 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 deArgs ...
."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 deNUMERIC
?@ 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 appelergenerate_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.