Есть ли уловки, чтобы использовать std :: cin для инициализации константной переменной?

Распространенное использование std :: cin

int X;
cin >> X;

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

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

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

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

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

// РЕДАКТИРОВАТЬ: Я хотел бы, чтобы решение было легко расширяемым для других типов (скажем, все POD, std::string и подвижно-копируемые классы с тривиальным конструктором) (если это не имеет смысла, пожалуйста, дайте мне знать в комментариях).

44

Решение

Я бы предпочел вернуть 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 пожары. Делегирование этой операции другой функции делает эту работу.

22

Другие решения

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

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

(Обратите внимание на дополнительные () в конце).

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

Редактировать: Поскольку в комментариях было указано, что это идет вразрез с правилом СУХОГО, вы можете воспользоваться auto а также 5.1.2:4 уменьшить повторение типов:

5.1.2:4 состояния:

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

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

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

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

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

Таким образом, мы могли бы изменить код так:

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

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

Изменить 2: В комментариях указывалось, что простое удаление имени типа там, где это возможно, не приводит к «СУХОМУ правильному» коду.
Кроме того, дедукция типа «трейлинг-возврат» в этом случае в настоящее время фактически является расширением MSVC ++, а также g ++ и не является (пока) стандартом.

19

Небольшая настройка лямбда-решения 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 (Перемещение конструктора подавлено оператором запятой):

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

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

13

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

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

Если у вас есть C ++ 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 */ }
}

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

3

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

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

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

Живой пример

2