2016-07-19 4 views
6

Supposons qu'il y ait une fonction avec une valeur par défaut:Comment traiter les valeurs par défaut dans les appels de fonction (profondément) imbriqués?

int foo(int x=42); 

Si cela est appelé par d'autres comme celui-ci:

int bar(int x=42) { return foo(x); } 
int moo(int x=42) { return bar(x); } 

Ceci est bien sûr juste un exemple artificiel. Cependant, j'ai parfois une situation assez similaire. Le paramètre est juste passé du plus haut niveau (moo) au plus bas et seulement là il est réellement utilisé. La mauvaise chose à ce sujet est que quand je change foo pour avoir un défaut différent de 42 je devrais rechercher tous les appelants et changer la valeur par défaut en conséquence.

Y a-t-il un motif/idiome pour éviter cette situation?

La seule solution simple qui me vient à l'esprit est

int bar()  { return foo(); } 
int bar(int x) { return foo(x); } 

Cependant, comme je suis un peu paresseux et dans le code réel cela conduirait à un certain duplication de code, je voudrais éviter cela.

+0

Je dirais que vous ne devriez pas vraiment utiliser la valeur par défaut du paramètre pour les valeurs par défaut arbitraires. Pour ceux que j'utiliserais les variables const namespaced. Les paramètres par défaut sont bons pour des choses comme les valeurs d'initialisation * sane * (zero/nullptr/false). – Galik

Répondre

2

Les solutions générales pratiques comprennent:

  • Utilisez une classe Optional_ pour l'argument, comme boost::optional ou un équivalent de bricolage.

  • Nom de la valeur par défaut (et utiliser le nom dans les définitions de fonctions d'emballage).

  • Surcharge chaque fonction de wrapper, comme vous l'avez montré dans la question.Répétez simplement la valeur par défaut dans les définitions de fonction wrapper, mais cela casse le principe DRY, ne vous répétez pas.


Dans un comment else-thread Tobi apporte le cas d'un emballage asdf défini comme

int asdf(int x=42,int y=42){ return foo(x)+foo(y);} 

En utilisant une classe Optional_:

auto foo(Optional_<int> x) 
    -> int 
{ return (x.is_empty()? 42 : x.value()); } 

auto asdf(Optional_<int> x = {}, Optional_<int> y = {}) 
    -> int 
{ return foo(x) + foo(y); } 

En utilisant une valeur par défaut nommée:

int const foo_default = 42; 

auto foo(int x = foo_default) 
    -> int 
{ return x; } 

auto asdf(int x = foo_default, int y = foo_default) 
    -> int 
{ return foo(x) + foo(y); } 

Utilisation: surcharge

auto foo(int x = 42) 
    -> int 
{ return x; } 

auto asdf() 
    -> int 
{ return foo() + foo(); } 

auto asdf(int x) 
    -> int 
{ return foo(x) + foo(); } 

auto asdf(int x, int y) 
    -> int 
{ return foo(x) + foo(y); } 

Il convient de noter que asdf ne peut pas être facilement défini comme un modèle de fonction transmettant ses arguments. En outre, un tel modèle ne peut pas être facilement défini dans une unité de traduction séparée, et on ne peut pas prendre son adresse. Pour ces raisons, je n'ai pas inclus cette solution possible dans la liste à puces: c'est très limité, pas une solution générale.

+0

'boost :: optional' * ou *' std :: optional' maintenant. – lorro

+0

Je ne suis pas sûr pourquoi je n'ai pas réalisé l'option d'une constante nommée moi-même ... parfois je pense juste trop compliqué (même pour C++) – user463035818

6

Je vous conseillerais de sélectionner l'une de ces deux options ci-dessous (comme vous pouvez le voir dans les autres réponses - il y a plus de solutions possibles).

  1. surcharge vos fonctions
  2. Définir constante

Ainsi, l'option 1 ressemble comme ci-dessous:

int foo(int x=42); 
int bar(int x) { return foo(x); } 
int moo(int x) { return bar(x); } 
int bar() { return foo(); } 
int moo() { return bar(); } 

Et, l'option 2 sera un peu plus court:

constexpr int FOO_DEFAULT = 42; 
int foo(int x=FOO_DEFAULT); 
int bar(int x=FOO_DEFAULT) { return foo(x); } 
int moo(int x=FOO_DEFAULT) { return bar(x); } 

J'utiliserais o ption-1 pour les cas avec petit nombre de valeurs par défaut (comme une valeur par défaut), option 2 pour les cas où vous avez un bon nombre de valeurs par défaut - comme foo(int a, int b = 3, std::string c = "wow", float pi = 3.14)

+0

Upvoted malgré le "Vous avez deux options à sélectionner", ce qui est incorrect. :) –

+0

À l'examen, supprimé le upvote en raison des identifiants ALL UPPERCASE (je n'ai pas remarqué, désolé). Toutes les majuscules sont susceptibles d'entrer en conflit avec les noms de macro. Cela enseigne donc un langage idiome C++: c'est OK en Java, et nécessaire en Python, mais très mauvais en C++. –

+0

@ Cheersandhth.-Alf Je crois OP si utiliser ma solution proposée choisira le nom en fonction de son propre standard de codage/style (le cas échéant). BTW - mon opinion sur ce sujet très spécifique est que le même style devrait être utilisé pour les macros variables et les variables const - parce que logiquement - il y a la même chose, ils sont utilisés pour atteindre le même objectif. – PiotrNycz

3

Vous pouvez éviter la duplication avec:

template<typename... T> 
    auto bar(T&&... t) { return foo(std::forward<T>(t)...); } 

Mais ce n'est pas une amélioration à mon humble avis. Arrêtez simplement d'être paresseux et définissez les surcharges qui appellent foo() lorsqu'aucun argument n'est fourni.

+1

** - 1 ** Cela change la nature de 'bar'. Maintenant, on ne peut pas prendre son adresse. On ne peut pas (pratiquement) l'implémenter dans une unité compilée séparément. En l'appelant avec un nombre peu élevé d'arguments ou de types d'arguments, on obtient des erreurs d'instanciation de modèle au lieu de diagnostics simples. –

+1

Comme je l'ai dit, ce n'est pas une amélioration. Mais il ** évite la duplication, ce que le PO voulait éviter. –

+1

Je pense que cela ne fonctionnera pas quand j'ai par exemple 'int asdf (int x = 42, int y = 42) {return toto (x) + toto (y);}' – user463035818