49

一般的な std::cin の使用法

int X;
cin >> X;

これの主な欠点は、X を にできないことconstです。バグを簡単に導入できます。そして、const値を作成して一度だけ書き込むことができるトリックを探しています。

素朴な解決策

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

const&X を;に変更することで明らかに改善できます。それでも、元の変数は変更できます。

これを行う方法の短くて賢い解決策を探しています。この質問に対する適切な回答から恩恵を受けるのは私だけではないと確信しています。

// 編集:ソリューションを他のタイプ (たとえば、すべての POD、および自明なコンストラクターを持つ移動可能でコピー可能なクラス) に簡単に拡張できるようにしたいと思います (std::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_lvaluestatic_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

そのような場合にラムダを利用できます。

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

(末尾の余分な () に注意してください)。

これには、別の関数を記述する代わりに、コードを読み取るときにソース ファイル内を移動する必要がないという利点があります。

編集:コメントでは、これは DRY ルールに反すると述べられているため、型の繰り返しを利用して減らすautoことができます。5.1.2:4

5.1.2:4状態:

[...] ラムダ式に末尾の戻り値の型が含まれていない場合、末尾の戻り値の型が次の型を示しているかのようになります。

  • 複合文が次の形式の場合

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

    左辺値から右辺値への変換 (4.1)、配列からポインターへの変換 (4.2)、および関数からポインターへの変換 (4.3) の後に返される式の型。

  • それ以外の場合は無効です。

したがって、コードを次のように変更できます。

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

ただし、型がラムダ本体内に「隠されている」ため、それが良いかどうかはわかりません...

編集 2:指摘されたコメントでは、可能であれば型名を削除するだけでは、「DRY 正しい」コードにはなりません。また、この場合の末尾の戻り型の推定は、現在、実際には MSVC++ と g++ の拡張であり、(まだ) 標準ではありません。

于 2012-09-05T10:52:39.307 に答える
14

lx. のラムダ ソリューションを微調整します。

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

もう1つの改善点。そうしないと、コンマ演算子は 12.8:31 で最適化を抑制します ( Move コンストラクターはコンマ演算子によって抑制されます)。

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

NRVO の恩恵を受ける可能性があるため、lx. のラムダよりも効率が低い可能性があることに注意してください。一方、最適化コンパイラは、副作用を伴わない移動を最適化できる必要があります。

于 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

C++11 を使用している場合、auto&&キーワードを使用すると、タイプを 1 回だけ指定できます。

auto&& X = in_get<int>();
于 2012-09-05T10:43:16.480 に答える
3

グローバル変数を初期化する必要があると思います。なぜなら、ローカル変数の場合、疑わしい値の定数を持つために、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 に答える