Existe-t-il des astuces pour utiliser std :: cin pour initialiser une variable const?

Utilisation courante de std :: cin

int X; cin >> X; 

Le principal inconvénient est que X ne peut pas être const . Il peut facilement introduire des bogues; et je cherche une astuce pour pouvoir créer une valeur const et y écrire une seule fois.

La solution naïve

 // Naive int X_temp; cin >> X_temp; const int X = X_temp; 

Vous pouvez évidemment l’améliorer en changeant X en const& ; cependant, la variable d’origine peut être modifiée.

Je cherche une solution courte et intelligente à la manière de procéder. Je suis sûr que je ne suis pas le seul à bénéficier d’une bonne réponse à cette question.

// EDIT: Je voudrais que la solution soit facilement extensible aux autres types (disons, tous les POD, std::ssortingng et les classes mobiles avec des constructeurs sortingviaux) (si cela n’a pas de sens, merci de me laisser savoir dans les commentaires).

J’opterais probablement pour renvoyer une optional , car le streaming pourrait échouer. Pour tester si c’est le cas (dans le cas où vous souhaitez affecter une autre valeur), utilisez get_value_or(default) , comme indiqué dans l’exemple.

 template boost::optional stream_get(Stream& s){ T x; if(s >> x) return std::move(x); // automatic move doesn't happen since // return type is different from T return boost::none; } 

Exemple en direct.

Pour garantir que l’utilisateur ne présente aucun mur de surcharge lorsque T ne peut pas être acheminé en entrée, vous pouvez écrire une classe de trait qui vérifie si le stream >> T_lvalue est valide et static_assert si ce n’est pas le cas:

 namespace detail{ template struct is_input_streamable_test{ template static auto f(U* u, Stream* s = 0) -> decltype((*s >> *u), int()); template static void f(...); static constexpr bool value = !std::is_void(0))>::value; }; template struct is_input_streamable : std::integral_constant::value> { }; template bool do_stream(T& v, Stream& s){ return s >> v; } } // detail:: template boost::optional stream_get(Stream& s){ using iis = detail::is_input_streamable; static_assert(iis::value, "T must support 'stream >> value_of_T'"); T x; if(detail::do_stream(x, s)) return std::move(x); // automatic move doesn't happen since // return type is different from T return boost::none; } 

Exemple en direct.

J’utilise une fonction detail::do_stream , sinon s >> x sera toujours analysé à l’intérieur de get_stream et vous aurez toujours le mur de surcharge à éviter lorsque static_assert . Déléguer cette opération à une autre fonction fait que cela fonctionne.

Vous pouvez utiliser des lambda pour de tels cas:

  const int x = []() -> int { int t; std::cin >> t; return t; }(); 

(Notez le extra () à la fin).

Au lieu d’écrire des fonctions distinctes, cela présente l’avantage de ne pas avoir à contourner votre fichier source lors de la lecture du code.

Edit: Puisque dans les commentaires, il a été déclaré que cela va à l’encontre de la règle DRY, vous pouvez tirer parti de auto et 5.1.2:4 pour réduire la répétition de type:

5.1.2:4 déclare:

[…] Si une expression lambda n’inclut pas un type de retour de fin, c’est comme si le type de retour de fin dénotait le type suivant:

  • si l’énoncé composé est de la forme

    { atsortingbute-specifier-seq(opt) return expression ; }

    le type de l’expression renvoyée après la conversion lvalue à rvalue (4.1), la conversion tableau à point (4.2) et la conversion fonction / pointeur (4.3);

  • sinon, nul.

Nous pourrions donc modifier le code pour qu’il ressemble à ceci:

  const auto x = [] { int t; std::cin >> t; return t; }(); 

Je ne peux pas décider si c’est mieux, puisque le type est maintenant “caché” dans le corps lambda …

Edit 2: Dans les commentaires, il a été souligné que le simple fait de supprimer le nom du type là où cela est possible ne donne pas lieu à un code “DRY-correct”. En outre, la déduction de type retour-retour dans ce cas est actuellement une extension de MSVC ++, ainsi que de g ++ et pas (encore) standard.

Une légère modification à la solution lambda de lx.:

 const int x = [](int t){ return iss >> t, t; }({}); 

Significativement moins de violation de DRY; peut être éliminé en changeant const int x en const auto x :

 const auto x = [](int t){ return iss >> t, t; }({}); 

Une autre amélioration vous pouvez convertir la copie en un mouvement, sinon l’opérateur de virgule supprime l’optimisation dans 12.8: 31 ( déplacer le constructeur supprimé par l’opérateur de virgule ):

 const auto x = [](int t){ return iss >> t, std::move(t); }({}); 

Notez que cela rest potentiellement moins efficace que le lambda de lx., car cela peut bénéficier de NRVO alors qu’il doit encore utiliser un constructeur de déplacement. D’un autre côté, un compilateur optimisant devrait être en mesure d’optimiser un mouvement sans effet secondaire.

Vous pouvez appeler une fonction pour renvoyer le résultat et initialiser dans la même instruction:

 template const T in_get (istream &in = std::cin) { T x; if (!(in >> x)) throw "Invalid input"; return x; } const int X = in_get(); const ssortingng str = in_get(); fstream fin("myinput.in",fstream::in); const int Y = in_get(fin); 

Exemple: http://ideone.com/kFBpT

Si vous avez C ++ 11, vous ne pouvez spécifier le type qu’une seule fois si vous utilisez le mot-clé auto&& .

 auto&& X = in_get(); 

Je suppose que vous voudrez initialiser une variable globale , car pour une variable locale, cela semble être un choix très délicat de renoncer à trois lignes d’énoncés simples et compréhensibles pour avoir une constante de valeur discutable.

Au niveau global, nous ne pouvons pas avoir d’erreurs lors de l’initialisation, nous devrons donc les gérer d’une manière ou d’une autre. Voici quelques idées.

Tout d’abord, une petite aide à la construction basée sur un modèle:

 template  T cinitialize(std::istream & is) noexcept { T x; return (is && is >> x) ? x : T(); } int const X = cinitialize(std::cin); 

Notez que les initialiseurs globaux ne doivent pas lancer d’exceptions (sous peine de std::terminate ) et que l’opération de saisie peut échouer. Tout compte fait, il est probablement assez mal conçu d’initialiser les variables globales à partir des saisies des utilisateurs. Une erreur fatale serait peut-être indiquée:

 template  T cinitialize(std::istream & is) noexcept { T x; if (!(is && is >> x)) { std::cerr << "Fatal error while initializing constants from user input.\n"; std::exit(1); } return x; } 

Juste pour clarifier ma position après une discussion dans les commentaires: Dans une perspective locale, je ne recourrais jamais à une béquille si maladroite. Étant donné que nous traitons des données externes fournies par l' utilisateur, nous devons essentiellement faire face aux défaillances dans le cadre du stream de contrôle normal:

 void foo() { int x; if (!(std::cin >> x)) { /* deal with it */ } } 

Je vous laisse le soin de décider si c'est trop difficile à écrire ou trop difficile à lire.

Bien sûr, vous pouvez faire cela simplement en construisant un istream_iterator temporaire. Par exemple:

 const auto X = *istream_iterator(cin) 

Il convient de souligner ici que vous abandonnez tout espoir de vérification des erreurs lorsque vous le faites. Qui généralement dans la prise d’entrée d’un utilisateur ne serait pas considéré comme le plus sage … mais bon, peut-être que vous avez organisé cette entrée en quelque sorte?

Exemple Live