web-dev-qa-db-ja.com

std :: cinを使用してconst変数を初期化するトリックはありますか?

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

int X;
cin >> X;

これの主な欠点は、Xをconstにすることができないことです。バグを簡単に引き起こす可能性があります。そして、私はconst値を作成し、一度だけそれに書き込むことができるいくつかのトリックを探しています。

素朴な解決策

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

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

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

// EDIT:ソリューションを他の型に簡単に拡張できるようにしたい(たとえば、すべてのPOD、std::string、および自明なコンストラクターを備えた移動可能なコピー可能なクラス)(もしそうなら意味がありません。コメントで教えてください)。

46

ストリーミングが失敗する可能性があるため、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
Xeo

あなたはそのような場合にラムダを利用することができます:

_   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 ++の拡張であり、(まだ)標準ではありません。

19
lx.

Lx。のラムダソリューションの微調整:

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

大幅に少ないDRY違反; const int xconst auto x

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

さらに1つの改善。コピーを移動に変換できます。それ以外の場合は、12.8:31でコンマ演算子が最適化を抑制します( コンマ演算子によって移動コンストラクターが抑制される )。

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

これは、lx。のラムダよりも効率が悪い可能性があることに注意してください。これは、NRVOのメリットを享受できるためです。一方、最適化コンパイラは、副作用のない動きを最適化できる必要があります。

13
ecatmur

もちろん、これは一時的な istream_iterator 。例えば:

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

これを行うと、エラーチェックのすべての希望を放棄することをここで指摘する価値があります。一般に、ユーザーからの入力を取得する際に最も賢明とは見なされません...しかし、ねえ、あなたはこの入力を何らかの方法でキュレートしたのでしょうか?

Live Example

5
Jonathan Mee

関数を呼び出して結果を返し、同じステートメントで初期化できます。

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

global変数を初期化することを想定していますが、ローカル変数の場合、定数を取得するために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;
}

コメントでの議論の後に私の立場を明確にするために:ローカルスコープでは、私はneverがそのような厄介な松葉杖に頼るでしょう。 external、user-supplied dataを処理しているので、基本的には通常の制御フローの一部として失敗に耐えなければなりません。

void foo()
{
    int x;

    if (!(std::cin >> x)) { /* deal with it */ }
}

書くのが多すぎるか、読むのが難しすぎるかは、あなた次第です。

3
Kerrek SB