49

常见的 std::cin 用法

int X;
cin >> X;

这样做的主要缺点是 X 不能const。它可以很容易地引入错误;我正在寻找一些技巧来创建一个 const 值,并且只写入一次。

天真的解决方案

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

您显然可以通过将 X 更改为const&;来改进它。仍然可以修改原始变量。

我正在寻找如何做到这一点的简短而聪明的解决方案。我确信我不是唯一一个会从这个问题的良好答案中受益的人。

// 编辑:我希望该解决方案能够轻松扩展到其他类型(比如说,所有 PODstd::string和具有简单构造函数的可移动可复制类)(如果没有意义,请在评论中告诉我) .

4

6 回答 6

24

我可能会选择返回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火灾时避免的重载墙。将此操作委托给不同的功能可以使这项工作正常进行。

于 2012-09-05T11:07:42.957 回答
20

对于这种情况,您可以使用 lambda:

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

(注意末尾的额外())。

与编写单独的函数不同,这样做的好处是在阅读代码时不必在源文件中跳转。

编辑:由于在评论中指出这违反了 DRY 规则,您可以利用auto5.1.2:4减少类型重复:

5.1.2:4状态:

[...] 如果 lambda-expression 不包含 trailing-return-type,就好像 trailing-return-type 表示以下类型:

  • 如果复合语句的形式是

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

    左值到右值转换(4.1)、数组到指针转换(4.2)和函数到指针转换(4.3)后返回表达式的类型;

  • 否则无效。

所以我们可以把代码改成这样:

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

不过,我无法确定这是否更好,因为该类型现在“隐藏”在 lambda 体内...

编辑 2:在评论中指出,仅在可能的情况下删除类型名称不会导致“DRY-correct”代码。此外,这种情况下的尾随返回类型扣除目前实际上是 MSVC++ 以及 g++ 的扩展,而不是(还)标准。

于 2012-09-05T10:52:39.307 回答
14

对 lx. 的 lambda 解决方案稍作调整:

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 中受益,而这仍然必须使用移动构造函数。另一方面,优化编译器应该能够优化出无副作用的移动。

于 2012-09-05T12:01:39.550 回答
6

您可以调用函数以返回结果并在同一语句中进行初始化:

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

auto&&如果您有 C++11,那么如果您使用关键字,则只能指定一次类型。

auto&& X = in_get<int>();
于 2012-09-05T10:43:16.480 回答
5

当然,您可以通过构建一个临时的istream_iterator. 例如:

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

值得在这里指出的是,当您执行此操作时,您将放弃所有错误检查的希望。通常,从用户那里获取输入不会被认为是最明智的......但是,嘿,也许你已经以某种方式策划了这个输入?

Live Example

于 2018-03-30T19:32:10.717 回答
3

我假设你会想要初始化一个全局变量,因为对于一个局部变量来说,放弃三行简单易懂的语句来获得一个有问题的常数似乎是一个非常尴尬的选择。

在全局范围内,我们不能在初始化中出现错误,所以我们必须以某种方式处理它们。这里有一些想法。

首先,一个模板化的小构造助手:

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 */ }
}

我让你来决定这是否写得太多或太难读。

于 2012-09-05T10:43:08.590 回答