Are there any tricks to use std::cin to initialize a const variable?

淺唱寂寞╮ 提交于 2019-11-27 06:52:55

I'd probably opt for returning an optional, since the streaming could fail. To test if it did (in case you want to assign another value), use get_value_or(default), as shown in the example.

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

Live example.

To further ensure that the user gets no wall-of-overloads presented when T is not input-streamable, you can write a trait class that checks if stream >> T_lvalue is valid and static_assert if it's not:

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

Live example.

I'm using a detail::do_stream function, since otherwise s >> x would still be parsed inside get_stream and you'd still get the wall-of-overloads that we wanted to avoid when the static_assert fires. Delegating this operation to a different function makes this work.

You could make use of lambdas for such cases:

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

(Note the extra () at the end).

Instead of writing a separate functions, this has the advantage of not having to jump around in your source file, when reading the code.

Edit: Since in the comments it was stated that this goes against the DRY rule, you could take advantage of auto and 5.1.2:4 to reduce type repetition:

5.1.2:4 states:

[...] If a lambda-expression does not include a trailing-return-type, it is as if the trailing-return-type denotes the following type:

  • if the compound-statement is of the form

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

    the type of the returned expression after lvalue-to-rvalue conversion (4.1), array-to-pointer conversion (4.2), and function-to-pointer conversion (4.3);

  • otherwise, void.

So we could alter the code to look like this:

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

I can't decide if that is better though, since the type is now "hidden" within the lambda body...

Edit 2: In the comments it was pointed out, that just removing the type name where it is possible, does not result in a "DRY-correct" code. Also the trailing-return-type deduction in this case is currently actually an extension of MSVC++ as well as g++ and not (yet) standard.

ecatmur

A slight tweak to lx.'s lambda solution:

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

Significantly less DRY violation; can be eliminated entirely by changing const int x to const auto x:

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

One further improvement; you can convert the copy into a move, since otherwise the comma operator suppresses the optimisation in 12.8:31 (Move constructor suppressed by comma operator):

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

Note that this is still potentially less efficient than lx.'s lambda, as that can benefit from NRVO whereas this still has to use a move constructor. On the other hand an optimising compiler should be able to optimise out a non-side-effect-bearing move.

You can call a function to return the result and initialize in the same statement:

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

Example: http://ideone.com/kFBpT

If you have C++11, then you can specify the type only once if you use the auto&& keyword.

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

I'm assuming that you will want to initialize a global variable, since for a local variable it just seems like a very awkward choice to forgo three lines of plain and understandable statements in order to have a constant of questionable value.

At the global scope, we can't have errors in the initialization, so we'll have to handle them somehow. Here are some ideas.

First, a templated little 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 that global initializers must not throw exceptions (under pain of std::terminate), and that the input operation may fail. All told, it's probably pretty bad design to initialize global variables from user input in such a fashion. Perhaps a fatal error would be indicated:

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

Just to clarify my position after some discussion in the comments: In a local scope I would never resort to such an awkward crutch. Since we're processing external, user-supplied data, we basically have to live with failure as part of the normal control flow:

void foo()
{
    int x;

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

I leave it up to you to decide whether that's too much to write or too hard too read.

Sure you can do this be just constructing a temporary istream_iterator. For example:

const auto X = *istream_iterator<int>(cin)

It's worth pointing out here that you're abandoning all hope of error checking when you do this. Which generally in the taking of input from a user would not be considered the wisest... but hey, maybe you've curated this input somehow?

Live Example

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!