0xe3aad

0xe3aad

テンプレートメタプログラミングからconstexpr(C++)まで

テンプレートメタプログラミング#

#include <cstdint>

template <uint64_t N> struct Fact {
    enum { Value = N * Fact<N - 1>::Value };
};

template <> struct Fact<1> {
    enum { Value = 1 };
};

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

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

template <> struct Fib<2> {
    enum { Value = 2 };
};

template <uint64_t base, uint64_t exp> struct Pow {
    enum { Value = base * Pow<base, exp - 1>::Value };
};

template <uint64_t base> struct Pow<base, 1> {
    enum { Value = base };
};

int main() {
    auto val1 = Fact<10>::Value;
    auto val2 = Fib<20>::Value;
    auto val3 = Pow<2, 10>::Value;
    return 0;
}

上記のコードスニペットでは、テンプレートを使用してコンパイル時の計算を実現しています(C++11以降でサポート)。

constexpr#

C++14からは、constexprを使用して関数を修飾することができるようになりました。これにより、定数を関数に渡すとコンパイラで計算され、変数を渡すと実行時に計算されます。これは、生成されるアセンブリコードからも確認できます。

#include <cstdint>

constexpr auto fact(uint64_t n) {
    if (n == 1) {
        return n;
    }
    return fact(n-1) * n;
}

int main() {
    constexpr auto val = fact(10);
    return 0;
}
fact(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L2
        mov     eax, DWORD PTR [rbp-4]
        jmp     .L3
.L2:
        mov     eax, DWORD PTR [rbp-4]
        sub     eax, 1
        mov     edi, eax
        call    fact(int)
        imul    eax, DWORD PTR [rbp-4]
.L3:
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     DWORD PTR [rbp-20], edi
        mov     QWORD PTR [rbp-32], rsi
        mov     DWORD PTR [rbp-4], 3628800
        mov     eax, DWORD PTR [rbp-20]
        mov     edi, eax
        call    fact(int)
        mov     DWORD PTR [rbp-8], eax
        mov     eax, 0
        leave
        ret

constexpr とテンプレートメタプログラミングの比較#

#include <cstdint>

constexpr auto fib(uint64_t n) {
    if (n <= 1) {
        return 1;
    }
    return fib(n-1) + fib(n-2);
}

int main(int argc, char *argv[]) {
    constexpr auto val = fib(40);
    return 0;
}

constexprの記述方法を使用すると、コンパイラエラーが発生する可能性があります。これは、この関数が多くの重複計算を行うためです。

一方、以下のテンプレートメタプログラミングの記述方法を使用すると、コンパイラがメモ化を行うため、重複計算が省略されます。

将来のコンパイラでは、constexpr関数のメモ化がサポートされる可能性があります。

template<uint64_t 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 = 1};
};

int main(int argc, char *argv[]) {
    auto val = Fib<40>::Value;
    return 0;
}

constexpr if#

C++17からは、constexpr ifがサポートされるようになりました。これにより、テンプレートメタプログラミングの記述方法を簡略化することができ、初期化のインスタンス化を省略することができます。これにより、複雑な計算もサポートされるようになります。なぜなら、テンプレートメタプログラミングはメモ化を行うからです。

#include <cstdint>

template<uint64_t N> struct Fib {
    static constexpr uint64_t Value = []{
        if constexpr (N <= 1) {
            return 1;
        } else {
            return Fib<N-1>::Value + Fib<N-2>::Value;
        }
    }();
};

int main(int argc, char *argv[]) {
    constexpr auto val = Fib<40>::Value;
    return 0;
}
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。