Подтвердить что ты не робот

Существуют ли какие-либо трюки для использования std:: cin для инициализации переменной const?

Общее использование std:: cin

int X;
cin >> X;

Основным недостатком этого является то, что X не может быть const. Он может легко вводить ошибки; и я ищу некоторый трюк, чтобы иметь возможность создать значение const и писать ему только один раз.

Наивное решение

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

Очевидно, вы могли бы улучшить его, изменив X на const&; Тем не менее исходная переменная может быть изменена.

Я ищу короткое и умное решение, как это сделать. Я уверен, что я не единственный, кто получит хороший ответ на этот вопрос.

//EDIT: Я хочу, чтобы решение было легко расширяемым для других типов (скажем, все POD, std::string и классы с возможностью перемещения с тривиальным конструктором) (если это не имеет смысла, пожалуйста, дайте мне знать в комментариях).

4b9b3361

Ответ 1

Я бы предпочел вернуть optional, поскольку потоковая передача может завершиться неудачей. Чтобы проверить, было ли это (если вы хотите присвоить другое значение), используйте get_value_or(default), как показано в примере.

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

Пример в реальном времени.

Чтобы еще больше убедиться в том, что пользователь не получает никаких перегрузок, когда T не является потокобезопасным, вы можете написать класс признаков, который проверяет, действительно ли stream >> T_lvalue и static_assert, если это не так:

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

Пример в реальном времени.

Я использую функцию detail::do_stream, так как иначе s >> x все равно будет разбираться внутри get_stream, и вы все равно получите перегрузку, которую мы хотели избежать, когда срабатывает static_assert. Делегирование этой операции на другую функцию делает эту работу.

Ответ 2

Вы можете использовать lambdas для таких случаев:

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

(Обратите внимание на extra() в конце).

Вместо написания отдельных функций это имеет то преимущество, что вам не нужно прыгать в исходном файле при чтении кода.

Изменить: Поскольку в комментариях было указано, что это противоречит правилу DRY, вы можете использовать auto и 5.1.2:4 для уменьшения повторения типа:

5.1.2:4:

[...] Если лямбда-выражение не включает тип возвращаемого-возвращаемого типа, это как если тип trailing-return-type обозначает следующий тип:

  • если составной оператор имеет вид

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

    тип возвращаемого выражения после преобразования lvalue-to-rvalue (4.1), преобразование матрицы в указатель (4.2) и преобразование функции в указатель (4.3);

  • в противном случае void.

Таким образом, мы могли бы изменить код, чтобы он выглядел следующим образом:

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

Я не могу решить, будет ли это лучше, поскольку тип теперь "скрыт" внутри тела лямбда...

Изменить 2: В комментариях было указано, что просто удаление имени типа, где это возможно, не приводит к "правильному" коду. Кроме того, вывод вывода trailing-return-type в этом случае фактически является расширением MSVС++, а также g++ и не стандартным стандартом.

Ответ 3

Небольшая настройка для lx. лямбда-раствор:

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

Значительно меньше DRY-нарушение; можно полностью устранить, изменив const int x на const auto x:

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

Еще одно улучшение; вы можете преобразовать копию в ход, так как в противном случае оператор запятой подавляет оптимизацию в 12.8: 31 (Переместить конструктор, подавленный оператором запятой):

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

Обратите внимание, что это по-прежнему потенциально менее эффективно, чем lx. lambda, так как это может выиграть от NRVO, тогда как это все равно должно использовать конструктор перемещения. С другой стороны, оптимизирующий компилятор должен иметь возможность оптимизировать перемещение без побочных эффектов.

Ответ 4

Вы можете вызвать функцию для возврата результата и инициализации в том же самом выражении:

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

Пример: http://ideone.com/kFBpT

Если у вас есть С++ 11, вы можете указать тип только один раз, если используете ключевое слово auto&&.

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

Ответ 5

Я предполагаю, что вы захотите инициализировать глобальную переменную, поскольку для локальной переменной просто кажется очень неудобным выбором отказаться от трех строк простых и понятных утверждений, чтобы иметь постоянную сомнительную ценность.

В глобальном масштабе мы не можем иметь ошибок в инициализации, поэтому нам придется как-то обращаться с ними. Вот несколько идей.

Во-первых, шаблонный вспомогательный помощник:

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

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

Обратите внимание, что глобальные инициализаторы не должны генерировать исключения (под болью std::terminate) и что операция ввода может завершиться неудачей. Все сказанное, вероятно, довольно плохой дизайн, чтобы таким образом инициализировать глобальные переменные из пользовательского ввода. Возможно, будет указана фатальная ошибка:

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

Просто, чтобы прояснить свою позицию после некоторого обсуждения в комментариях: в местном масштабе я бы никогда не прибегнул к такому неловкому костылю. Поскольку мы обрабатываем внешние данные, предоставленные пользователем, мы в основном должны жить с ошибкой как часть нормального потока управления:

void foo()
{
    int x;

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

Я оставляю это для вас, чтобы решить, слишком ли слишком много писать или слишком сложно читать.

Ответ 6

Конечно, вы можете просто создать временный istream_iterator. Например:

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

Стоит отметить, что вы отказываетесь от всякой надежды на проверку ошибок, когда вы это делаете. Который вообще в принятии ввода от пользователя не считался самым мудрым... но эй, может быть, вы каким-то образом купили этот вход?

Live Example