そのため、これはもう少し複雑になっています。これも少しXY 問題ですが、少なくともここでは X と Y の両方について明確にしています。
提案されたアプローチから始めましょう。これは決してうまくいきません:
std::get<i>(_t);
get
は関数テンプレートであるためi
、整数定数式である必要があります。つまり、i
コンパイル時に認識されている必要があります。
提案されたソリューションは基本的に に基づいているため、ICEtuple
を作成できないと、すべてが解けてバラバラになります。i
そこで、提案されたアプローチを忘れて、問題をもう一度見てみましょう。おそらくフィールドのように見えるものに分かれていると思われる、たくさんのものを含むファイルがあります。これらのフィールドは (私が知る限り) さまざまな種類のデータを表しています。これがそのようなファイルの例であるとします:
IBM
123.45
1000
ここには、文字列、浮動小数点数、および整数があります。異なるファイルは完全に異なるデータを持つ可能性があり、あるファイルの特定の位置にあるデータは、別のファイルの同じ位置にあるデータと同じ型ではない場合があります。次に、これらの異なるファイルで初期化する必要があるさまざまなクラスの束があり、それぞれがファイル内のさまざまな位置からプルされた、さまざまな型のさまざまなデータ メンバーの独自のコレクションを持っています。うん。
問題の複雑さを考えると、私の自然な傾向は、解決策をできるだけ単純に保つことです。ここにはすでに十分な複雑さがあります。私が考えることができる最も簡単なアプローチは、LineParser
解析したいファイルの種類ごとに異なる具体的なクラスを持つことです。ただし、これにより、さまざまな種類のファイルが多数ある場合にコードが肥大化し、その数が増えるにつれて保守が指数関数的に難しくなります。だから、あなたがそれをしたくないという前提で続けましょう。
ただし、増加しないことの 1 つは、ファイル内のさまざまな種類のフィールドの数です。最終的には、文字列、整数、浮動小数点数、およびドメインに固有のその他の特別なものなど、実際にはいくつかしかありません。ただし、データ ファイルを追加しても、フィールドの種類の数は比較的一定のままです。もう 1 つの定数はファイル自体です。これは文字データです。では、それを活用しましょう。
ファイル ストレージ タイプ (ここでは文字データを想定しています) からさまざまなフィールドに変換するいくつかの無料関数を実装します。Boost を使用している場合は、これを使用lexical_cast
してほとんどのことを実行できます。それ以外の場合は、stringstream
または他のものを使用できます。考えられる実装の 1 つを次に示します。他にも多くの実装があります。
template <typename Return> Return As (const std::string& val)
{
std::stringstream ss;
ss << val;
Return retval;
ss >> retval;
return retval;
}
Base
ここで、特定の-type クラスについて、関心のあるフィールドの位置とタイプがわかっていると想定しています。これらは不変です。たとえばBase
、株価を表す の場合、最初のフィールドがティッカー シンボルであり、文字列であることがわかっています。
FileParser
ファイルからすべてを取り出し、ファイル内のフィールドごとに 1 つの要素を配列内の文字データとしてキャッシュするだけであれば、クラスをジェネリックにすることができます。繰り返しますが、ここには多くの可能な実装があります。私の焦点は設計であり、実際のコードではありません。
class LineParser
{
private:
std::vector <string> mItems;
public:
LineParser(const std::string & fileName)
{
std::ifstream fs(fileName);
std::copy(
std::istream_iterator<int>(fs),
std::istream_iterator<int>(),
std::back_inserter(mItems));
}
std::string GetAt (size_t i) const
{
return mItems [i];
}
};
Base
コンストラクターで、各const
データ メンバーに対して、特定のアイテムを からプルLineParser
し、自由関数で変換します。
class Base
{
private:
const std::string mTicker;
const uint32_t mSize;
const float mPrice;
public:
Base (const LineParser& parser)
:
mTicker (As <std::string> (parser.GetAt (0))), // We know the ticker is at field 0
mPrice (As <float> (parser.GetAt (1))), // Price is at field 1...
mSize (As <uint32_t> (parser.GetAt (2))
{
}
};
このアプローチには、私が気に入っている点がいくつかあります。1 つは、関係するクラスと関数が多数あるにもかかわらず、それぞれが単純であることです。ここにある小さなギズモにはそれぞれ、明確に定義された 1 つの責任があり、あまり多くのことをしようとはしません。
別の理由として、ビジネス ロジック コードの自己文書化は簡潔であり、それが属する場所:const
メンバーが初期化されるコード内:
Base (const LineParser& parser)
:
mTicker (As <std::string> (parser.GetAt (0))), // We know the ticker is at field 0
mPrice (As <float> (parser.GetAt (1))), // Price is at field 1...
mSize (As <uint32_t> (parser.GetAt (2))
{
}
mTicker
たとえば、イニシャライザは、「ティッカー シンボルは文字列であり、ファイルの位置 1 から取得されます」と言います。掃除。