8

here で説明されているように、最適化のためにテンプレートを使用したいと思います。ただし、bool テンプレート引数の数が増えると、テンプレートのインスタンス化で分岐が多すぎる可能性があります。また、ブール値の代わりに大きな列挙型を使用すると、さらに枝分かれします。

#include <iostream>
using namespace std;

template <bool b1, bool b2>
int HeavyLoop_impl(int arg)
{
    for (int i = 0; i < 10000000; i++)
    {
        // b1 is known at compile-time, so this branch will be eliminated
        if (b1) { arg += 1; }
        else    { arg += 2; }

        // b2 is known at compile-time, so this branch will be eliminated
        if (b2) { arg += 10; }
        else    { arg += 20; }
    }
    return arg;
}

// This function could be generated automatically
void HeavyLoop(bool b1, bool b2, int arg)
{
    int res;
    if (b1) {
        if (b2) { res = HeavyLoop_impl<true, true>(arg); }
        else    { res = HeavyLoop_impl<true, false>(arg); }
    } else {
        if (b2) { res = HeavyLoop_impl<false, true>(arg); }
        else    { res = HeavyLoop_impl<false, false>(arg); }
    }
    cout << "res: "<<res<<endl;
}

int main(int argc, char**argv)
{
    bool b1 = true;
    bool b2 = false;
    int arg = 0;
    HeavyLoop(b1, b2, arg);
    return 0;
}

HeavyLoop 関数を自動的に生成する方法はありますか? 私はこのようなものが欲しいです:

vars_to_template_function<bool, bool>(HeavyLoop_impl, b1, b2, arg);

それはどういうわけか可能でしょうか?ヒントをありがとう。

注: これは非常に単純化された例にすぎません。もちろん、実際のループはもっと複雑です:o)

4

6 に答える 6

6

私はコードをもっと楽しむことにしました。これは、最初の試みよりも改善されたバージョンであり、次の利点があります。

  • enumタイプをサポート
  • 変換するパラメーターの数を明示的に指定する
  • 複雑な部分の一般的な実装、それを使用する関数ごとに 1 つの小さなヘルパー。

コード:

#include <iostream>
#include <utility>
#include <type_traits>

// an enum we would like to support
enum class tribool { FALSE, TRUE, FILE_NOT_FOUND };

// declare basic generic template
// (independent of a specific function you'd like to call)
template< template< class > class CB, std::size_t N, typename = std::tuple<> >
struct var_to_template;

// register types that should be supported
template< template< class > class CB, std::size_t N, typename... Cs >
struct var_to_template< CB, N, std::tuple< Cs... > >
{
    // bool is pretty simple, there are only two values
    template< typename R, typename... Args >
    static R impl( bool b, Args&&... args )
    {
        return b
          ? var_to_template< CB, N-1, std::tuple< Cs..., std::true_type > >::template impl< R >( std::forward< Args >( args )... )
          : var_to_template< CB, N-1, std::tuple< Cs..., std::false_type > >::template impl< R >( std::forward< Args >( args )... );
    }

    // for each enum, you need to register all its values
    template< typename R, typename... Args >
    static R impl( tribool tb, Args&&... args )
    {
        switch( tb ) {
        case tribool::FALSE:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::FALSE > > >::template impl< R >( std::forward< Args >( args )... );
        case tribool::TRUE:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::TRUE > > >::template impl< R >( std::forward< Args >( args )... );
        case tribool::FILE_NOT_FOUND:
          return var_to_template< CB, N-1, std::tuple< Cs..., std::integral_constant< tribool, tribool::FILE_NOT_FOUND > > >::template impl< R >( std::forward< Args >( args )... );
        }
        throw "unreachable";
    }

    // in theory you could also add int, long, ... but
    // you'd have to switch on every possible value that you want to support!
};

// terminate the recursion
template< template< class > class CB, typename... Cs >
struct var_to_template< CB, 0, std::tuple< Cs... > >
{
    template< typename R, typename... Args >
    static R impl( Args&&... args )
    {
        return CB< std::tuple< Cs... > >::template impl< R >( std::forward< Args >( args )... );
    }
};

// here's your function with the template parameters
template< bool B, tribool TB >
int HeavyLoop_impl( int arg )
{
    for( int i = 0; i < 10000000; i++ ) {
        arg += B ? 1 : 2;
        arg += ( TB == tribool::TRUE ) ? 10 : ( TB == tribool::FALSE ) ? 20 : 30;
    }
    return arg;
}

// a helper class, required once per function that you'd like to forward
template< typename > struct HeavyLoop_callback;
template< typename... Cs >
struct HeavyLoop_callback< std::tuple< Cs... > >
{
    template< typename R, typename... Args >
    static R impl( Args&&... args )
    {
        return HeavyLoop_impl< Cs::value... >( std::forward< Args >( args )... );
    }
};

// and here, everything comes together:
int HeavyLoop( bool b, tribool tb, int arg )
{
    // you provide the helper and the number of arguments
    // that should be converted to var_to_template<>
    // and you provide the return type to impl<>
    return var_to_template< HeavyLoop_callback, 2 >::impl< int >( b, tb, arg );
}

int main()
{
    bool b = true;
    tribool tb = tribool::FALSE;
    int arg = 0;
    int res = HeavyLoop( b, tb, arg );
    std::cout << "res: " << res << std::endl;
    return 0;
}

そして、あなたがそれを試してみたい場合に備えて、ここにライブの例があります。

于 2013-10-07T10:23:18.533 に答える
3

方法は次のとおりです。

#include <iostream>
using namespace std;

template <bool b1, bool b2>
struct HeavyLoopImpl
{
    static int func(int arg)
    {
        for (int i = 0; i < 10000000; i++) {
            arg += b1 ? 1 : 2;
            arg += b2 ? 10 : 20;
        }
        return arg;
    }
};

template <template<bool...> class Impl,bool...Bs>
struct GenericJump
{
    template<typename... Args>
    static int impl(Args&&... args)
    {
        return Impl<Bs...>::func(std::forward<Args>(args)...);
    }

    template<typename... Args>
    static int impl(bool b, Args&&... args)
    {
        return b
            ? GenericJump<Impl,Bs...,true >::impl(std::forward<Args>(args)...)
            : GenericJump<Impl,Bs...,false>::impl(std::forward<Args>(args)...);
    }
};

int HeavyLoop(bool b1, bool b2, int arg)
{
    return GenericJump<HeavyLoopImpl>::impl(b1,b2,arg);
}

int main()
{
    bool b1 = true;
    bool b2 = false;
    int arg = 0;
    int res = HeavyLoop(b1, b2, arg);
    cout << "res: "<<res<<endl;
    return 0;
}

これは基本的に Daniels のソリューションですが、実装以外の機能を使用できますHeavyLoop_impl()。単一のテンプレート関数を呼び出すことしかできないということは、一般的なソリューションであるという目的を無効にします。テンプレート クラスは他のGenericJump関数も呼び出すことができます。HeavyLoop_impl()テンプレート関数を静的関数を持つテンプレート クラスに変更するだけfunc()です。それは素晴らしく機能します。gcc 4.7.3 でコンパイルされ、正しい出力が得られます。

于 2013-10-07T10:51:19.117 に答える
0

あなたの質問に対する最良の答えは、実際には自動的に生成せず、質問に既にあるようにそのままにしておくことだと思います。

妥協点を生成する自動テンプレート関数を作成すると、最初に行っている不変の切り替えが難読化されます。

私は、人々があなたに提供したどの回答よりも、あなたの質問で中間層がどのように機能するかを理解しようとすることをはるかに好みます.

同様の例があります。私の場合、値の配列間でさまざまな操作を適用できます。配列は同じサイズです。ただし、配列のサブ範囲を操作に影響する重み値でマップする構造もあります。たとえば、100 個の値の配列を操作していて、次のような重みを持つ範囲があるとします。

[0,25] rangeWeight = 0
[26,35] rangeWeight = 0.25
[36,50] rangeWeight = 0.5
[51,99] rangeWeight = 1.0

したがって、各操作は次のようになります (疑似):

for each subrange:
    alias to the dst buffer
    alias to the src buffer
    determine the number of elements in the range
    if there's any
        weight = weightPassedIn * rangeWeight;

        Op(dst, src, weight, numElements);

私にとっては、目的地に触れているかどうかに関係するいくつかの最適化がありました (まだクリアされた値にある場合は、操作ごとの計算を単純化するためにいくつかの仮定を行うことができます)。 、他のショートカットがあります。

最初は、すべて同じセットアップで同じループを何度も書いていました。各操作の周りのすべてのループを関数にリファクタリングすると、ほとんど自然に不変ラッパーの形になりました。実際には、主にループ内で発生する高レベルの操作をラップするだけで、それ以外の場合は次のように個々の最適化を処理するラッパーがいくつかあることが判明しました。

if (weight == 1.0f)
{
    if ( arrayIsCleared )
        Blend<BlendOpSet, true, false>(otherBuff, subRangesMask, 1.0f);
    else
        Blend<BlendOpAccumulate, true, false>(otherBuff, subRangesMask, 1.0f);
}
else
{
    if ( arrayIsCleared )
        Blend<BlendOpSet, false, false>(otherBuff, subRangesMask, weight);
    else
        Blend<BlendOpAccumulate, false, false>(otherBuff, subRangesMask, weight);
}
于 2014-02-08T05:54:16.790 に答える
0

列挙型も処理できる、boost::hana を使用した別のソリューションを次に示します。

#include <cstdio>
#include <type_traits>
#include <boost/hana.hpp>

namespace hana = boost::hana;

template <typename F, typename TArgs, typename TIn, typename TOut>
void fun_arg_combinations_impl(F&& f, TArgs targs, TIn tin, TOut tout) {
    if constexpr (hana::is_empty(tin)) {
        hana::unpack(tout, f);
    } else {
        hana::for_each(hana::front(tin), [&](auto v){
            if (v == hana::front(targs)) {
                fun_arg_combinations_impl(f, hana::drop_front(targs), hana::drop_front(tin), hana::append(tout, v));
            }
        });
    }
}

template <typename F, typename TArgs, typename TIn>
void fun_arg_combinations(F&& f, TArgs targs, TIn tin) {
    fun_arg_combinations_impl(f, targs, tin, hana::tuple<>());
}

enum Shape {LINE, CIRCLE, SQUARE};

int main()
{
    auto f_heavy_loop = [](auto b1t, auto b2t, auto st) {
        constexpr bool b1 = decltype(b1t)::value;
        constexpr bool b2 = decltype(b2t)::value;
        constexpr Shape s = decltype(st )::value;

        printf("out:%d %d %d\n", b1, b2, s);
    };

    //constexpr auto bools = hana::make_tuple(std::true_type{}, std::false_type{});
    constexpr auto bools = hana::tuple<std::true_type, std::false_type>{};
    constexpr auto shapes = hana::tuple<
        std::integral_constant<Shape, LINE>,
        std::integral_constant<Shape, CIRCLE>,
        std::integral_constant<Shape, SQUARE>>{};

    // Using volatile to not allow the compiler to optimize for hard-coded values
    volatile bool b1 = true;
    volatile bool b2 = false;
    volatile Shape s = SQUARE;
    fun_arg_combinations(
        f_heavy_loop,
        hana::make_tuple(b1   , b2   , s     ),
        hana::make_tuple(bools, bools, shapes));
}

b1b2およびラムダs内は allであるため、それらで使用できます。f_heavy_loop()constexprif constexpr

出力:

out:1 0 2

ここで生成されたアセンブリを見てください: https://godbolt.org/z/nsF2l5

于 2019-10-04T12:38:12.403 に答える