Существуют ли какие-либо трюки для использования std :: cin для инициализации переменной const?
Общее использование std :: cin
int X; cin >> X;
Основным недостатком этого является то, что X не может быть const
. Он может легко вводить ошибки; и я ищу некоторый трюк, чтобы иметь возможность создать значение const и писать ему только один раз.
Наивное решение
- Разница между iostream и iostream.h
- Почему чтение записей структурных полей из std :: istream завершается неудачно, и как я могу это исправить?
- Использование flush () перед закрытием ()
- Что такое заголовок ?
- Как напечатать двойное значение с полной точностью с помощью cout?
// Naive int X_temp; cin >> X_temp; const int X = X_temp;
Вы, очевидно, могли бы улучшить его, изменив X на const&
; Тем не менее исходная переменная может быть изменена.
Я ищу короткое и умное решение, как это сделать. Я уверен, что я не единственный, кто получит хороший ответ на этот вопрос.
// EDIT: я хотел бы, чтобы решение было легко расширяемым для других типов (скажем, все POD, std::string
и movable-copyable classes с тривиальным конструктором) (если это не имеет смысла, пожалуйста, позвольте мне знать в комментариях).
- Почему C ++ STL iostreams не «исключение»?
- Чтение из текстового файла до тех пор, пока EOF не повторит последнюю строку
- Почему мы будем называть cin.clear () и cin.ignore () после чтения ввода?
- Как прочитать полную строку от пользователя, используя cin?
- Как читать строки за строкой или весь текстовый файл сразу?
- Вывод строк unicode в консольном приложении Windows
- Скрыть пользовательский ввод при запросе пароля
- Почему iostream :: eof внутри условия цикла считается неправильным?
Вероятно, я предпочел бы вернуть optional
, поскольку streamовая передача может завершиться неудачей. Чтобы проверить, было ли это (если вы хотите назначить другое значение), используйте get_value_or(default)
, как показано в примере.
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; }
Живой пример.
Чтобы еще больше убедиться, что пользователь не получает никаких перегрузок, представленных в том случае, если T
не является streamом данных, вы можете написать class признаков, который проверяет, действительно ли stream >> T_lvalue
и static_assert
если это не так:
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; }
Живой пример.
Я использую функцию detail::do_stream
, так как иначе s >> x
все равно будет разбираться внутри get_stream
и вы все равно получите перегрузку, которую мы хотели бы избежать, когда static_assert
. Делегирование этой операции на другую функцию делает эту работу.
Вы можете использовать lambda для таких случаев:
const int x = []() -> int { int t; std::cin >> t; return t; }();
(Обратите внимание на дополнительный () в конце).
Вместо написания отдельных функций это имеет то преимущество, что вам не нужно прыгать в исходном файле при чтении кода.
Изменить: поскольку в комментариях было указано, что это противоречит правилу DRY, вы можете воспользоваться auto
и 5.1.2:4
чтобы уменьшить повторение типа:
5.1.2:4
состояния:
[…] Если lambda-выражение не включает тип trailing-return-type, это похоже на то, что тип trailing-return-type обозначает следующий тип:
если составной оператор имеет вид
{ attribute-specifier-seq(opt) return expression ; }
тип возвращаемого выражения после преобразования lvalue-to-rvalue (4.1), преобразования от matrix к указателю (4.2) и преобразования функции в указатель (4.3);
в противном случае, void.
Поэтому мы могли бы изменить код, чтобы он выглядел так:
const auto x = [] { int t; std::cin >> t; return t; }();
Я не могу решить, лучше ли это, потому что тип теперь «скрыт» внутри тела лямбды …
Изменить 2: в комментариях было указано, что просто удаление имени типа, где это возможно, не приводит к «правильному» правилу. Кроме того, вывод вывода trailing-return-type в этом случае на самом деле является расширением MSVC ++, а также g ++ и не (пока) стандартом.
Небольшая настройка lambda-решения lx.:
const int x = [](int t){ return iss >> t, t; }({});
Значительно менее суровое нарушение; можно полностью устранить, изменив const int x
на const auto x
:
const auto x = [](int t){ return iss >> t, t; }({});
Еще одно улучшение; вы можете преобразовать копию в ход, поскольку в противном случае оператор запятой подавляет оптимизацию в 12.8: 31 ( Move constructor, подавленный оператором запятой ):
const auto x = [](int t){ return iss >> t, std::move(t); }({});
Обратите внимание, что это по-прежнему потенциально менее эффективно, чем lambda, так как это может выиграть от NRVO, тогда как это все еще должно использовать конструктор перемещения. С другой стороны, оптимизирующий компилятор должен иметь возможность оптимизировать перемещение без побочных эффектов.
Вы можете вызвать функцию для возврата результата и инициализации в том же самом выражении:
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);
Пример: http://ideone.com/kFBpT
Если у вас есть C ++ 11, вы можете указать тип только один раз, если вы используете ключевое слово auto&&
.
auto&& X = in_get();
Я предполагаю, что вы захотите инициализировать глобальную переменную, так как для локальной переменной просто кажется очень неудобным выбором отказаться от трех строк простых и понятных утверждений, чтобы иметь постоянную сомнительную ценность.
В глобальном масштабе мы не можем иметь ошибок в инициализации, поэтому нам придется как-то их обрабатывать. Вот некоторые идеи.
Во-первых, шаблонный помощник по строительству:
template T cinitialize(std::istream & is) noexcept { T x; return (is && is >> x) ? x : T(); } int const X = cinitialize(std::cin);
Обратите внимание, что глобальные инициализаторы не должны генерировать исключения (под болью std::terminate
) и что операция ввода может завершиться неудачей. Все сказанное, вероятно, довольно плохой дизайн, чтобы таким образом инициализировать глобальные переменные из пользовательского ввода. Возможно, будет указана фатальная ошибка:
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; }
Просто чтобы прояснить свою позицию после некоторого обсуждения в комментариях: в местном масштабе я бы никогда не прибегнул к такому неловкому костылю. Поскольку мы обрабатываем внешние данные, предоставленные пользователем, мы в основном должны работать с ошибкой как часть обычного streamа управления:
void foo() { int x; if (!(std::cin >> x)) { /* deal with it */ } }
Я оставляю это для вас, чтобы решить, слишком ли много писать или слишком сильно читать.
Конечно, вы можете сделать это, просто istream_iterator
временный istream_iterator
. Например:
const auto X = *istream_iterator(cin)
Здесь стоит отметить, что вы отказываетесь от всякой надежды на проверку ошибок, когда вы это делаете. Который вообще в принятии ввода от пользователя не считался самым мудрым … но эй, может быть, вы куратором этого ввода каким-то образом?
Живой пример