使用std::cin初始化const变量有什么技巧吗

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

本文关键字:什么 const std cin 初始化 使用 变量      更新时间:2023-10-16

常见std::cin用法

int X;
cin >> X;

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

天真的解决方案

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

通过将X改为const&,可以明显地改善它;尽管如此,原始变量还是可以修改的。

我正在寻找一个简短而聪明的解决方案来解决这个问题。我相信我不是唯一一个会从这个问题的好答案中受益的人。

//EDIT:我希望该解决方案能够轻松扩展到其他类型(比如说,所有POD、std::string和带有琐碎构造函数的可移动可复制类)(如果没有意义,请在评论中告诉我)。

我可能会选择返回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触发时,您仍然会得到我们想要避免的过载墙。将此操作委派给不同的函数可以使其工作。

在这种情况下,您可以使用lambdas:

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

(请注意末尾多余的())。

与编写单独的函数不同,它的优点是在读取代码时不必在源文件中跳转。

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

5.1.2:4状态:

[…]如果lambda表达式不包括尾部返回类型,则为如果尾部返回类型表示以下类型:

  • 如果复合语句的形式为

    { 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++的扩展,而不是(尚未)标准。

对lx进行了轻微调整s 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的效率。"s lambda,因为这可以从NRVO中受益,而这仍然需要使用move构造函数。另一方面,优化编译器应该能够优化出无副作用的轴承移动。

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

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

当然,只需构建一个临时istream_iterator就可以做到这一点。例如:

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

这里值得指出的是,当你这样做时,你放弃了所有错误检查的希望。这通常在接受用户输入时不会被认为是最明智的。。。但嘿,也许你不知怎么策划了这个输入?

实时示例

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

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

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

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

我让你来决定这是太难写还是太难读。