有没有使用std :: cin来初始化const变量的技巧?

时间:2012-09-05 10:40:37

标签: c++ c++11 iostream

常见的std :: cin用法

int X;
cin >> X;

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

天真的解决方案

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

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

我正在寻找一个如何做到这一点的简短而聪明的解决方案。我相信我并不是唯一一个能从这个问题的答案中获益的人。

//编辑:我希望解决方案可以轻松扩展到其他类型(例如,所有POD,std::string和可移动复制具有琐碎构造函数的类)(如果它没有意义,请在评论中告诉我。)

6 个答案:

答案 0 :(得分:23)

我可能会选择返回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;
}

Live example.

为了进一步确保用户在T不可输入流式传输时不会出现重载墙,您可以编写一个特征类来检查stream >> T_lvalue是否有效以及{{1}如果不是:

static_assert

Live example.

我正在使用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。将此操作委派给不同的函数可以实现这一点。

答案 1 :(得分:19)

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

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

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

这不是编写单独的函数,而是在阅读代码时不必在源文件中跳转。

修改:因为在评论中声明这违反了DRY规则,您可以利用auto5.1.2:4来减少类型重复:

5.1.2:4州:

  

[...]如果lambda表达式不包含trailing-return-type,则为as   如果trailing-return-type表示以下类型:

     
      
  • 如果复合语句的格式为

         

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

         

    lvalue-to-rvalue转换后返回表达式的类型(4.1),   数组到指针的转换(4.2)和函数到指针的转换(4.3);

  •   
  • 否则,无效。

  •   

所以我们可以改变代码看起来像这样:

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

我无法判断这是否更好,因为该类型现在已经“隐藏”在lambda体内......

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

答案 2 :(得分:13)

略微调整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(Move constructor suppressed by comma operator)中禁止优化:

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

请注意,这仍然可能比lx。的lambda效率低,因为它可以从NRVO中受益,而这仍然需要使用移动构造函数。另一方面,优化编译器应该能够优化非副作用的移动。

答案 3 :(得分:5)

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

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

答案 4 :(得分: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 */ }
}

我让你自己决定是否写得太多或太难阅读。

答案 5 :(得分:2)

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

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

值得指出的是,当你这样做时,你已经放弃了所有错误检查的希望。一般来说,从用户那里获取意见不会被认为是最明智的...但是,嘿,也许你已经以某种方式策划了这个输入?

Live Example