¿Hay algún truco para usar std :: cin para inicializar una variable const?

Uso común de std :: cin

int X; cin >> X; 

La principal desventaja de esto es que X no puede ser const . Puede fácilmente introducir errores; y estoy buscando algún truco para poder crear un valor constante, y escribirle solo una vez.

La solución ingenua

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

Obviamente, podrías mejorar cambiando X a const& ; aún así, la variable original puede ser modificada.

Estoy buscando una solución corta e inteligente de cómo hacer esto. Estoy seguro de que no soy el único que se beneficiará de una buena respuesta a esta pregunta.

// EDIT: me gustaría que la solución sea fácilmente extensible a los otros tipos (digamos, todos los POD, std::string y clases de copia movible con constructor trivial) (si no tiene sentido, por favor déjame saber en los comentarios).

Probablemente optaría por devolver una optional , ya que la transmisión podría fallar. Para probar si lo hizo (en caso de que quiera asignar otro valor), use get_value_or(default) , como se muestra en el ejemplo.

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

Ejemplo en vivo

Para garantizar aún más que el usuario no tenga una nube de sobrecarga presentada cuando T no es transmisible por entrada, puede escribir una clase de rasgo que verifique si la stream >> T_lvalue es válida y static_assert si no lo es:

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

Ejemplo en vivo

Estoy usando una función detail::do_stream , ya que de lo contrario, s >> x aún se analizaría dentro de get_stream y aún obtendría el muro de sobrecarga que queríamos evitar cuando se static_assert el static_assert . Delegar esta operación a una función diferente hace que esto funcione.

Puede usar lambdas para tales casos:

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

(Tenga en cuenta el extra () al final).

En lugar de escribir funciones separadas, tiene la ventaja de no tener que saltar en su archivo de origen cuando lee el código.

Editar: como en los comentarios se dijo que esto va en contra de la regla DRY, se puede aprovechar el auto y 5.1.2:4 para reducir la repetición del tipo:

5.1.2:4 estados:

[…] Si una expresión lambda no incluye un tipo de retorno final, es como si el tipo de retorno final denotara el siguiente tipo:

  • si el enunciado compuesto es de la forma

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

    el tipo de la expresión devuelta después de la conversión lvalue-to-rvalue (4.1), la conversión de matriz a puntero (4.2) y la conversión de función a puntero (4.3);

  • de lo contrario, vacío.

Entonces, podríamos alterar el código para que se vea así:

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

No puedo decidir si eso es mejor, ya que el tipo está ahora “oculto” dentro del cuerpo lambda …

Edición 2: en los comentarios se indicó que eliminar el nombre del tipo donde es posible no da como resultado un código “DRY-correct”. También la deducción del tipo de retorno final en este caso actualmente es en realidad una extensión de MSVC ++ así como de g ++ y no (todavía) estándar.

Un ligero ajuste a la solución lambda de lx.

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

Significativamente menos violación de DRY; puede eliminarse por completo al cambiar const int x a const auto x :

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

Una mejora más; puede convertir la copia en un movimiento, ya que, de lo contrario, el operador de coma suprime la optimización en 12.8: 31 (el operador de coma suprime el constructor de movimientos ):

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

Tenga en cuenta que esto todavía es potencialmente menos eficiente que la lambda de lx., Ya que puede beneficiarse de NRVO mientras que esto todavía tiene que usar un constructor de movimiento. Por otro lado, un comstackdor de optimización debería ser capaz de optimizar un movimiento que no tenga efectos secundarios.

Puede llamar a una función para devolver el resultado e inicializar en la misma statement:

 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 string str = in_get(); fstream fin("myinput.in",fstream::in); const int Y = in_get(fin); 

Ejemplo: http://ideone.com/kFBpT

Si tiene C ++ 11, puede especificar el tipo solo una vez si usa la palabra clave auto&& .

 auto&& X = in_get(); 

Supongo que querrá inicializar una variable global , ya que para una variable local, parece una elección muy incómoda renunciar a tres líneas de enunciados simples y comprensibles para tener una constante de valor cuestionable.

En el ámbito global, no podemos tener errores en la inicialización, por lo que tendremos que manejarlos de alguna manera. Aquí hay algunas ideas.

Primero, un pequeño ayudante de construcción con plantilla:

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

Tenga en cuenta que los inicializadores globales no deben arrojar excepciones (bajo pena de std::terminate ), y que la operación de entrada puede fallar. En resumen, probablemente sea un diseño bastante malo inicializar las variables globales a partir de la entrada del usuario de esa manera. Quizás un error fatal estaría indicado:

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

Solo para aclarar mi posición después de una discusión en los comentarios: en el ámbito local, nunca recurriría a una muleta tan torpe. Dado que estamos procesando datos externos proporcionados por el usuario , básicamente tenemos que vivir con fallas como parte del flujo de control normal:

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

Dejo en tus manos decidir si eso es demasiado para escribir o demasiado difícil de leer.

Claro que puedes hacer esto simplemente construyendo un istream_iterator temporal. Por ejemplo:

 const auto X = *istream_iterator(cin) 

Vale la pena señalar aquí que está abandonando toda esperanza de comprobación de errores cuando hace esto. Lo cual, en general, al recibir información de un usuario no sería considerado el más sabio … pero bueno, ¿tal vez has curado esta información de alguna manera?

Ejemplo en vivo