44 votes

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

Commune de std::cin utilisation

int X;
cin >> X;

Le principal inconvénient de cette est que X ne peut pas être const. Il peut facilement introduire des bugs, et je suis à la recherche d'une astuce pour être en mesure de créer un const valeur, et écrire une seule fois.

La solution naïve

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

Vous pourriez évidemment l'améliorer en changeant X const&; encore, la variable d'origine peut être modifié.

Je suis à la recherche d'un short et de la solution intelligente pour le faire. Je suis sûr que je ne suis pas le seul à bénéficier d'une bonne réponse à cette question.

// EDIT: j'aimerais la solution pour être facilement extensible à d'autres types (disons, toutes les Gousses, std::string et mobile copiable classes avec trivial constructeur) (si ça n'a pas de sens, s'il vous plaît laissez-moi savoir dans les commentaires).

22voto

Xeo Points 69818

Je serais probablement opter pour retourner un optional, depuis la diffusion en continu peut échouer. Pour tester si il l'a fait (dans le cas où vous souhaitez affecter une autre valeur), utilisez get_value_or(default), comme le montre l'exemple.

template<class T, class Stream>
boost::optional<T> 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 vivant.

Pour veiller à ce que l'utilisateur reçoit pas de mur-de-surcharges présenté lors de l' T n'est pas entrée en flux, vous pouvez écrire un trait de classe qui vérifie si stream >> T_lvalue est valide et static_assert si elle n'est pas:

namespace detail{
template<class T, class Stream>
struct is_input_streamable_test{
  template<class U>
  static auto f(U* u, Stream* s = 0) -> decltype((*s >> *u), int());
  template<class>
  static void f(...);

  static constexpr bool value = !std::is_void<decltype(f<T>(0))>::value;
};

template<class T, class Stream>
struct is_input_streamable
  : std::integral_constant<bool, is_input_streamable_test<T, Stream>::value>
{
};

template<class T, class Stream>
bool do_stream(T& v, Stream& s){ return s >> v; }
} // detail::

template<class T, class Stream>
boost::optional<T> stream_get(Stream& s){
  using iis = detail::is_input_streamable<T, Stream>;
  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 vivant.

Je suis à l'aide d'un detail::do_stream de la fonction, car, autrement, s >> x serait encore être analysé à l'intérieur d' get_stream et que vous souhaitez tout de même obtenir le mur de surcharges que nous voulions éviter lors de l' static_assert des incendies. Déléguer cette opération à une autre fonction qui fait ce travail.

18voto

lx. Points 1492

Vous pourriez faire usage de lambdas pour de tels cas:

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

(Note supplémentaire (à la fin).

Au lieu d'écrire une des fonctions distinctes, ce qui a l'avantage de ne pas avoir à naviguer dans votre fichier source, lors de la lecture du code.

Edit: Depuis dans les commentaires il a été dit que cela va à l'encontre de la SEC de la règle, vous pourriez profiter d' auto et 5.1.2:4 afin de réduire le type de répétition:

5.1.2:4 membres de:

[...] Si une lambda-expression n'inclut pas de fuite-de retour, c'est comme si la fuite-retour-type indique le type suivant:

  • si le composé-déclaration est de la forme

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

    le type de retour de l'expression après lvalue-à-rvalue de conversion (4.1), tableau de pointeur de conversion (4.2), et la fonction de pointeur de conversion (4.3);

  • sinon, nulle.

Donc, nous pourrions modifier le code 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 "caché" dans le corps de lambda...

Edit 2: Dans les commentaires, il a été souligné, que de retirer le nom du type où il est possible, ne résulte pas en un SEC "correcte" du code. Aussi les de fuite-retour-type de déduction dans cette affaire est en fait une extension de MSVC++ ainsi que g++ et pas (encore) standard.

12voto

ecatmur Points 64173

Un léger réglage lx. s'lambda solution:

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

Beaucoup moins SEC violation; peut être totalement éliminée en changeant const int x de const auto x:

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

Une amélioration supplémentaire; vous pouvez convertir le copier à un déplacement, car sinon l'opérateur virgule supprime l'optimisation 12,8:31 (constructeur de Déplacement supprimée par l'opérateur virgule):

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

Note que c'est encore potentiellement moins efficace que la lx. s'lambda, comme pouvant bénéficier de la NRVO alors que cela a encore d'utiliser un constructeur de déplacement. D'autre part, une optimisation du compilateur devrait être en mesure d'optimiser un non-effet secondaire de roulement de la déplacer.

5voto

ronalchn Points 7215

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

 template<typename T>
const T in_get (istream &in = std::cin) {
    T x;
    if (!(in >> x)) throw "Invalid input";
    return x;
}

const int X = in_get<int>();
const string str = in_get<string>();

fstream fin("myinput.in",fstream::in);
const int Y = in_get<int>(fin);
 

Exemple: http://ideone.com/kFBpT

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

 auto&& X = in_get<int>();
 

3voto

Kerrek SB Points 194696

Je suis en supposant que vous souhaitez initialiser un mondial variable, puisque, pour une variable locale, il semble juste comme un très maladroit choix de renoncer à trois lignes de plaine et compréhensible déclarations pour avoir une constante de valeur douteuse.

Au niveau global, nous ne pouvons pas avoir des erreurs lors de l'initialisation, de sorte que nous allons les traiter en quelque sorte. Voici quelques idées.

Tout d'abord, basé sur un modèle de construction helper:

template <typename T>
T cinitialize(std::istream & is) noexcept
{
    T x;
    return (is && is >> x) ? x : T();
}

int const X = cinitialize<int>(std::cin);

Note globale des initialiseurs ne doit pas jeter des exceptions (sous peine d' std::terminate), et que la contribution de l'opération peut échouer. Tout compte fait, il est probablement assez mauvais design d'initialiser des variables globales à l'entrée de l'utilisateur dans ce mode. Peut-être une erreur fatale serait indiqué:

template <typename T>
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 discussion dans les commentaires: Dans une portée locale, je voudrais ne jamais recourir à une telle maladroit béquille. Puisque nous sommes de traitement externe, fourni par l'utilisateur de données, nous avons à vivre avec l'échec comme une partie de la commande normale de flux:

void foo()
{
    int x;

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

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X