web-dev-qa-db-ja.com

実行時にテンプレートメタプログラミングのコンパイル時定数を取得する

バックグラウンド

次のことを考慮してください。

template <unsigned N>
struct Fibonacci
{
    enum
    {
        value = Fibonacci<N-1>::value + Fibonacci<N-2>::value
    };
};

template <>
struct Fibonacci<1>
{
    enum
    {
        value = 1
    };
};

template <>
struct Fibonacci<0>
{
    enum
    {
        value = 0
    };
};

これは一般的な例であり、フィボナッチ数列の値をコンパイル時の定数として取得できます。

int main(void)
{
    std::cout << "Fibonacci(15) = ";
    std::cout << Fibonacci<15>::value;
    std::cout << std::endl;
}

しかし、明らかに実行時に値を取得することはできません。

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    // ensure the table exists up to a certain size
    // (even though the rest of the code won't work)
    static const unsigned fibbMax = 20;
    Fibonacci<fibbMax>::value;

    // get index into sequence
    unsigned fibb = std::Rand() % fibbMax;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << Fibonacci<fibb>::value;
    std::cout << std::endl;
}

fibbはコンパイル時の定数ではないためです。

質問

だから私の質問は:

実行時にこのテーブルを確認する最良の方法は何ですか?最も明白な解決策(そして「解決策」は軽く取られるべきです)は、大きなswitchステートメントを持つことです:

unsigned fibonacci(unsigned index)
{
    switch (index)
    {
    case 0:
        return Fibonacci<0>::value;
    case 1:
        return Fibonacci<1>::value;
    case 2:
        return Fibonacci<2>::value;
    .
    .
    .
    case 20:
        return Fibonacci<20>::value;
    default:
        return fibonacci(index - 1) + fibonacci(index - 2);
    }
}

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    static const unsigned fibbMax = 20;    

    // get index into sequence
    unsigned fibb = std::Rand() % fibbMax;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << fibonacci(fibb);
    std::cout << std::endl;
}

しかし、現在、テーブルのサイズは非常にハードコーディングされており、40と言うように拡張するのは簡単ではありません。

私が思いついた唯一のクエリ方法は次のとおりです。

template <int TableSize = 40>
class FibonacciTable
{
public:
    enum
    {
        max = TableSize
    };

    static unsigned get(unsigned index)
    {
        if (index == TableSize)
        {
            return Fibonacci<TableSize>::value;
        }
        else
        {
            // too far, pass downwards
            return FibonacciTable<TableSize - 1>::get(index);
        }
    }
};

template <>
class FibonacciTable<0>
{
public:
    enum
    {
        max = 0
    };

    static unsigned get(unsigned)
    {
        // doesn't matter, no where else to go.
        // must be 0, or the original value was
        // not in table
        return 0;
    }
};

int main(void)
{
    std::srand(static_cast<unsigned>(std::time(0)));

    // get index into sequence
    unsigned fibb = std::Rand() % FibonacciTable<>::max;

    std::cout << "Fibonacci(" << fibb << ") = ";
    std::cout << FibonacciTable<>::get(fibb);
    std::cout << std::endl;
}

これはうまくいくようです。私が目にする唯一の2つの問題は次のとおりです。

  • Fibonacci <2>を計算するには、TableMaxを2まで実行する必要があるため、コールスタックが大きくなる可能性があります。

  • 値がテーブルの外にある場合、計算ではなくゼロを返します。

だから私が欠けているものはありますか?実行時にこれらの値を選択するためのより良い方法があるはずです。

おそらく、特定の数までのswitchステートメントを生成するswitchステートメントのテンプレートメタプログラミングバージョンですか?

前もって感謝します。

43
GManNickG
_template <unsigned long N>
struct Fibonacci
{
    enum
    {
        value = Fibonacci<N-1>::value + Fibonacci<N-2>::value
    };
    static void add_values(vector<unsigned long>& v)
    {
        Fibonacci<N-1>::add_values(v);
        v.Push_back(value);
    }
};

template <>
struct Fibonacci<0>
{
    enum
    {
        value = 0
    };
    static void add_values(vector<unsigned long>& v)
    {
        v.Push_back(value);
    }

};

template <>
struct Fibonacci<1>
{
    enum
    {
        value = 1
    };
    static void add_values(vector<unsigned long>& v)
    {
        Fibonacci<0>::add_values(v);
        v.Push_back(value);
    }
};



int main()
{
    vector<unsigned long> fibonacci_seq;
    Fibonacci<45>::add_values(fibonacci_seq);
    for (int i = 0; i <= 45; ++i)
        cout << "F" << i << " is " << fibonacci_seq[i] << '\n';
}
_

問題をよく考えた後、私はこの解決策を思いつきました。もちろん、実行時に値をコンテナーに追加する必要がありますが、(重要なことですが)実行時に値は計算済みではありません。

補足として、_Fibonacci<1>_を_Fibonacci<0>_の上に定義しないことが重要です。そうしないと、_Fibonacci<0>::add_values_への呼び出しを解決するときに__Fibonacci<0>_への呼び出しを解決するときに、コンパイラがveryと混同します。 (SOMECODE)__のテンプレートの特殊化が指定されていません。

もちろん、TMPには制限があります。事前計算された最大値が必要であり、実行時に値を取得するには再帰が必要です(テンプレートが再帰的に定義されているため)。

28
rlbond

私はこの質問が古いことを知っていますが、それは私に興味をそそられ、実行時に動的コンテナーが満たされない状態で実行する必要がありました:

#ifndef _FIBONACCI_HPP
#define _FIBONACCI_HPP


template <unsigned long N>
struct Fibonacci
{
    static const unsigned long long value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;

    static unsigned long long get_value(unsigned long n)
    {
        switch (n) {
            case N:
                return value;
            default:
                return n < N    ? Fibonacci<N-1>::get_value(n)
                                : get_value(n-2) + get_value(n-1);
        }
    }
};

template <>
struct Fibonacci<0>
{
    static const unsigned long long value = 0;

    static unsigned long long get_value(unsigned long n)
    {
        return value;
    }
};

template <>
struct Fibonacci<1>
{
    static const unsigned long long value = 1;

    static unsigned long get_value(unsigned long n)
    {
        return value;
    }
};

#endif

これは機能しているようで、最適化を使用してコンパイルすると(それを許可するかどうかは不明です)、コールスタックは深くなりません-値(引数)n> Nの場合、スタックには通常のランタイム再帰がありますここで、Nはテンプレートのインスタンス化で使用されるTableSizeです。ただし、TableSizeを下回ると、生成されたコードは、コンパイル時に計算された定数、または最悪の場合、ジャンプテーブル(-c -g -Wa、-adhlns = mainを指定してgccでコンパイルされた)を介して「計算された」値に置き換えられます。 sとリストを確認しました)、switchステートメントが明示的に実行されると同じです。

このように使用すると:

int main()
{
    std::cout << "F" << 39 << " is " << Fibonacci<40>::get_value(39) << '\n';
    std::cout << "F" << 45 << " is " << Fibonacci<40>::get_value(45) << '\n';
}

最初のケース(コンパイル時に計算された値)では計算の呼び出しはまったくなく、2番目のケースでは呼び出しスタックの深さが最悪です。

fibtest.exe!Fibonacci<40>::get_value(unsigned long n=41)  Line 18 + 0xe bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=42)  Line 18 + 0x2c bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=43)  Line 18 + 0x2c bytes    C++
fibtest.exe!Fibonacci<40>::get_value(unsigned long n=45)  Line 18 + 0xe bytes    C++
fibtest.exe!main()  Line 9 + 0x7 bytes    C++
fibtest.exe!__tmainCRTStartup()  Line 597 + 0x17 bytes    C

つまり「テーブル」で値が見つかるまで再帰します。 (デバッガーの逆アセンブリを1行ずつステップ実行することによって、またテストintを乱数<= 45に置き換えることによって検証されます)

再帰部分は、線形反復解に置き換えることもできます。

static unsigned long long get_value(unsigned long n)
{
    switch (n) {
        case N:
            return value;    
        default:
            if (n < N) {
                return Fibonacci<N-1>::get_value(n);
            } else {
                // n > N
                unsigned long long i = Fibonacci<N-1>::value, j = value, t;
                for (unsigned long k = N; k < n; k++) {
                    t = i + j;
                    i = j;
                    j = t;
                }
                return j;
            }
    }
}
17
madoki

可変テンプレート(C++ 0x標準)をサポートするC++コンパイラーがある場合、コンパイル時にfibonaciiシーケンスをタプルに保存できます。実行時に、インデックスを作成することにより、そのタプルから任意の要素にアクセスできます。

#include <Tuple>   
#include <iostream>

template<int N>
struct Fib
{
    enum { value = Fib<N-1>::value + Fib<N-2>::value };
};

template<>
struct Fib<1>
{
    enum { value = 1 };
};

template<>
struct Fib<0>
{
    enum { value = 0 };
};

// ----------------------
template<int N, typename Tuple, typename ... Types>
struct make_fibtuple_impl;

template<int N, typename ... Types>
struct make_fibtuple_impl<N, std::Tuple<Types...> >
{
    typedef typename make_fibtuple_impl<N-1, std::Tuple<Fib<N>, Types... > >::type type;
};

template<typename ... Types>
struct make_fibtuple_impl<0, std::Tuple<Types...> >
{
    typedef std::Tuple<Fib<0>, Types... > type;
};

template<int N>
struct make_fibtuple : make_fibtuple_impl<N, std::Tuple<> >
{};

int main()
{
   auto tup = typename make_fibtuple<25>::type();
   std::cout << std::get<20>(tup).value;  
   std::cout << std::endl; 

   return 0;
}
4
sigidagi

C++ 11の場合:std::arrayと簡単なゲッター: https://ideone.com/F0b4D

namespace detail
{

template <std::size_t N>
struct Fibo :
    std::integral_constant<size_t, Fibo<N - 1>::value + Fibo<N - 2>::value>
{
    static_assert(Fibo<N - 1>::value + Fibo<N - 2>::value >= Fibo<N - 1>::value,
                  "overflow");
};

template <> struct Fibo<0u> : std::integral_constant<size_t, 0u> {};
template <> struct Fibo<1u> : std::integral_constant<size_t, 1u> {};

template <std::size_t ... Is>
constexpr std::size_t fibo(std::size_t n, index_sequence<Is...>)
{
    return const_cast<const std::array<std::size_t, sizeof...(Is)>&&>(
        std::array<std::size_t, sizeof...(Is)>{{Fibo<Is>::value...}})[n];
}

template <std::size_t N>
constexpr std::size_t fibo(std::size_t n)
{
    return n < N ?
        fibo(n, make_index_sequence<N>()) :
        throw std::runtime_error("out of bound");
}
} // namespace detail

constexpr std::size_t fibo(std::size_t n)
{
    // 48u is the highest
    return detail::fibo<48u>(n);
}
3
Jarod42

C(および大部分はC++)の基本的なテナントの1つは、不要なものに対しては料金を支払わないということです。

ルックアップテーブルの自動生成は、コンパイラが行う必要のあることではありません。その機能が必要な場合でも、他のすべての人が必ずしも必要とするわけではありません。

ルックアップテーブルが必要な場合は、ルックアップテーブルを作成するプログラムを作成します。次に、そのデータをプログラムで使用します。

実行時に値を計算する場合は、テンプレートメタプログラムを使用せず、通常のプログラムを使用して値を計算してください。

0
James Caccese

プリプロセッサのメタプログラミング手法を使用して、スイッチまたは静的配列を生成できます。複雑さがそのアプローチの制限を超えず、コードまたはデータを生成する追加の手順でツールチェーンを拡張したくない場合は、適切な決定です。

0
jmihalicza