C ++のget_from_variant関数

Oct 24 2020

これは、C ++でのstd :: variant実装を使用してネストされたさまざまなタイプのrecursive_transform関数のフォローアップの質問です。G. Sliepenの答えは、操作のみを再帰的に残して変換する、言及したためにrecursive_transform()良いアイデアかもしれません。その結果、recursive_transform関数の実装は次の形式で維持されます。さらに、前方宣言は削除されました。

template<class T, class _Fn> requires is_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
    T returnObject = input;

    std::transform(input.begin(), input.end(), returnObject.begin(), func);
    return returnObject;
}

template<class T, class _Fn> requires is_iterable<T> && is_element_iterable<T>
static inline T recursive_transform(const T input, _Fn func)
{
    T returnObject = input;
    std::transform(input.begin(), input.end(), returnObject.begin(),
        [func](const auto& element)
        {
            return recursive_transform(element, func);
        }
    );
    return returnObject;
}

ただし、範囲とstd::variant、などの複合構造を処理したいのですstd::vector<std::variant<double>>。get_from_variantこれらの操作に集中するために、新しい機能が頭に浮かびます。

template<typename T_variant, typename T>
static inline auto get_from_variant(T_variant input_variant)
{
    T return_val;
    std::visit([&](auto&& arg)
        {
            return_val = static_cast<T>(arg);
            return arg;
        },
        input_variant);
    return return_val;
}

このget_from_variant関数のテスト:

int main()
{
    //  get_from_variant function test
    std::variant<double> testNumber = 1;
    std::cout << get_from_variant<decltype(testNumber), double>(testNumber);
    
    //  The usage of recursive_transform function and get_from_variant function
    std::variant<double> variant_number = 3.14;
    
    std::vector<decltype(variant_number)> testVector1;
    testVector1.push_back(variant_number);
    testVector1.push_back(variant_number);
    testVector1.push_back(variant_number);
    std::cout << get_from_variant<std::variant<double>, double>(recursive_transform(testVector1, [](auto x){ return get_from_variant<std::variant<double>, double>(x) + 1; }).at(0)) << std::endl;
    
    return 0;
}

すべての提案を歓迎します。

  • フォローアップはどの質問ですか?

    C ++でのstd :: variant実装を使用してネストされたさまざまなタイプのrecursive_transform関数

  • 前回の質問以降、コードにどのような変更が加えられましたか?

    より良い方法でstd::variantなど、範囲との複合構造を処理するためにstd::vector<std::variant<double>>、新しい関数get_from_variantが作成されました。

  • なぜ新しいレビューが求められているのですか?

    私の意見では、関数のデザインget_from_variantが良いかどうかわかりませんか?アイデアや使い方は良いですか?コメントは大歓迎です。

回答

2 Quuxplusone Oct 24 2020 at 23:08

私は最初からこのスレッドをフォローしていなかったので、読者がこの時点で期待するよりも混乱しています。質問の焦点をコードの小さな部分に留めておきたい場合は、Godboltリンクのように、毎回完全なコンパイル可能な例を提供することをお勧めします。

実際、私はGodboltリンクを表示することを好みます(正しく行ったように質問のコードを表示することに加えて)。これにより、コードをGodboltに貼り付ける手間が省けます。:)ここにあなたのコードへのリンクがあります:Godbolt。


std::variant<double> testNumber = 1;

これはC ++ 20ではコンパイルされません。以前は使用しましたか?もしそうなら、そうです、それはC ++にとってかなり大きなAPIブレークです...しかし、あなたの問題ではありません。とにかく、に変更して1.0再コンパイルしてください。


template<typename T_variant, typename T>
static inline auto

を失いますstatic inline。テンプレートは定義上事実上インラインであり、このテンプレートを静的にしたくありません。各翻訳ユニットに独自のコピーを保持させたくありません(インライン化によって最適化されていない場合)。

私はのファンではありませんGiraffe_case。テンプレートパラメータ名は短くする必要がありCamelCaseます; ここで私はお勧めしVます。

あなたのstd::visitラムダには役に立たないがありreturn arg;ます。実際、この関数全体は次のようになります。

template<class V, class T>
auto get_from_variant(V input) {
    return std::visit([&](auto&& arg) {
        return static_cast<T>(arg);
    }, input);
}

残骸が取り除かれると、脳細胞は次のレベルの衒学者に自由に集中できるようになります。arg参照(auto&&)を転送することで取得しますが、実際にはに転送しませんstatic_cast。たぶんstatic_cast<T>(static_cast<decltype(arg)>(arg))ここで使用する必要があります。そうすればarg、が右辺値参照である場合、それはTのコンストラクターに移動されますか?

ちょっと待って; 左辺値にアクセスしているため、右辺値の参照にargなることはありませんinput。したがって、arg訪問する場所を変更することを期待するべきではないかもしれません—それをと見なすことができますconst auto& arg。しかし、変更する予定がない場合はinput、おそらくそれを取る必要があります—ええ、ちょっと待ってください、なぜinputここのコピーを作成するのですか?そもそもconstリファレンスでそれを取ってください!

template<class V, class T>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

[&]キャプチャを必要としないため、ラムダからを削除しました。

テンプレートパラメータも確認する必要がありますget_from_variant。V推論することはできますが、Tできません。控除できないパラメータを最初に置くことは常に常に理にかなっています。

template<class T, class V>
auto get_from_variant(const V& input) {
    return std::visit([](const auto& arg) {
        return static_cast<T>(arg);
    }, input);
}

これで、メインドライバーは次のようになります。

std::variant<double> testNumber = 1.0;
std::cout << get_from_variant<double>(testNumber);
    
std::vector testVector1 = {
    std::variant<double>(3.14),
    std::variant<double>(3.14),
    std::variant<double>(3.14),
};
std::cout << get_from_variant<double>(
    recursive_transform(testVector1, [](const auto& x){
        return get_from_variant<double>(x) + 1;
    }).at(0)
) << std::endl;

一方、ではrecursive_transform、タイプミスconst T inputがありますconst T& input。これらのタイプミスを機械的にgrepできますが、そうする必要があります。

  • 繰り返しますが、static inlineテンプレートから削除します。

  • 名前_Fnは実装用に予約されています。を使用するだけFです。

  • funcラムダにコピーする必要はありません。作成する[&]すべてのラムダのデフォルトとして使用する必要があります(上記のように、plainで回避できる場合を除く[])。

  • あなたがない限り正直、熱狂的なSTLのイディオムを以下について、単にコールバックを渡すFconst参照で、それをコピーし、これまで避けてください。C ++には、ステートフルで変更可能なコールバック用の場所がありますが、その場所でtransformはありません。

  • 基本ケースは、必要以上に複雑です。それを修正しましょう。

すべてを一緒に入れて:

template<class T, class F>
T recursive_transform(const T& input, const F& f) {
    return f(input);
}

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    T returnObject = input;
    std::transform(input.begin(), input.end(), returnObject.begin(),
        [&](const auto& element) {
            return recursive_transform(element, f);
        }
    );
    return returnObject;
}

そして、std::transformここを使用するのはやり過ぎだと私には本当に思えます。1input 回はコピーを作成し、もう1回は変換を行うために2回読み取ります。このように、オープンコーディングしたとしましょう。

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    T output = input;
    for (auto&& elt : output) {
        elt = recursive_transform(elt, f);
    }
    return output;
}

もちろん、我々は可能性のような何かをする20の範囲C ++を使用して、この:

template<class T, class F> requires is_iterable<T>
T recursive_transform(const T& input, const F& f) {
    auto transformed = input | std::views::transform([&](auto&& x) {
        return recursive_transform(x, f);
    });
    return T(transformed.begin(), transformed.end());
}

これはコンパイルに時間T::value_typeがかかり、より大きなコードを生成します—ただし、コピーの割り当てを排除しているため、コピーにコストがかかる場合は、実行時に実際に高速になる可能性があります—T::value_type直接その場で構築しているだけです。