web-dev-qa-db-ja.com

C ++およびDでのメタプログラミング

C++のテンプレートメカニズムは、誤ってテンプレートメタプログラミングに役立ちました。一方、Dは特にこれを容易にするために設計されました。そして、どうやらそれは理解するのがさらに簡単です(または私は聞いたことがあるので)。

私はDの経験はありませんが、好奇心旺盛です。テンプレートのメタプログラミングに関して、Dでできること、C++ではできないことは何ですか。

65
Paul Manta

Dでのテンプレートメタプログラミングを支援する2つの最大の要素は、テンプレート制約とstatic ifです。どちらもC++は理論的に追加でき、これは非常に有益です。

テンプレート制約を使用すると、テンプレートをインスタンス化できるようにするためにtrueでなければならない条件をテンプレートに設定できます。たとえば、これはstd.algorithm.findのオーバーロードの1つのシグネチャです。

R find(alias pred = "a == b", R, E)(R haystack, E needle)
    if (isInputRange!R &&
        is(typeof(binaryFun!pred(haystack.front, needle)) : bool))

このテンプレート関数をインスタンス化できるようにするには、型Rstd.range.isInputRangeで定義されている入力範囲である必要があります(したがって、isInputRange!Rtrueでなければなりません) 、および指定された述語は、指定された引数でコンパイルされ、暗黙的にboolに変換可能な型を返すバイナリ関数である必要があります。テンプレート制約の条件の結果がfalseの場合、テンプレートはコンパイルされません。これにより、テンプレートが指定された引数でコンパイルされない場合にC++で発生する厄介なテンプレートエラーから保護されるだけでなく、テンプレートの制約に基づいてテンプレートをオーバーロードできるようになります。たとえば、findの別のオーバーロードがあります。

R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isForwardRange!R1 && isForwardRange!R2
        && is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
        && !isRandomAccessRange!R1)

引数はまったく同じですが、制約が異なります。したがって、異なる型は同じテンプレート関数の異なるオーバーロードで機能し、findの最適な実装を各型に使用できます。 C++でそのようなことをきれいに行う方法はありません。通常のテンプレート制約で使用される関数とテンプレートに少し精通しているので、Dのテンプレート制約はかなり読みやすいですが、C++で非常に複雑なテンプレートメタプログラミングが必要な場合でも、このようなものを試してみると、平均的なプログラマではできません理解できるようになる、ましてや実際に自分で行う。ブーストは、この典型的な例です。それはいくつかの素晴らしいことをしますが、それは信じられないほど複雑です。

static ifは状況をさらに改善します。テンプレート制約の場合と同様に、コンパイル時に評価できるすべての条件を使用できます。例えば.

static if(isIntegral!T)
{
    //...
}
else static if(isFloatingPoint!T)
{
    //...
}
else static if(isSomeString!T)
{
    //...
}
else static if(isDynamicArray!T)
{
    //...
}
else
{
    //...
}

コンパイルされるブランチは、最初にtrueと評価される条件によって異なります。そのため、テンプレート内で、テンプレートのインスタンス化に使用されたタイプに基づいて、またはコンパイル時に評価できる他の何かに基づいて、その実装の一部を特殊化できます。たとえば、core.time

static if(is(typeof(clock_gettime)))

システムがclock_gettimeを提供するかどうかに基づいて異なる方法でコードをコンパイルします(clock_gettimeがある場合はそれを使用し、それ以外の場合はgettimeofdayを使用します)。

恐らく、Dがテンプレートを改善した最も目立った例は、私のチームがC++で遭遇した問題です。与えられた型が特定の基本クラスから派生したものかどうかに基づいて、テンプレートを異なる方法でインスタンス化する必要がありました。 このスタックオーバーフローの質問 に基づくソリューションを使用することになりました。機能しますが、あるタイプが別のタイプから派生しているかどうかをテストするだけではかなり複雑です。

ただし、Dでは、:演算子を使用するだけで済みます。例えば.

auto func(T : U)(T val) {...}

Tが暗黙的にUに変換できる場合(TUから派生した場合と同様)、funcはコンパイルされます。一方、Tが暗黙的にUに変換できない場合、変換できません。 That単純な改善により、基本的なテンプレートの特殊化でさえも(テンプレートの制約やstatic ifがなくても)より強力になります。

個人的には、コンテナや<algorithm>の関数を除いて、C++でテンプレートを使用することはめったにありません。テンプレートを使用するのは非常に面倒だからです。それらは醜いエラーを引き起こし、空想的なことを行うことは非常に困難です。少しでも複雑なことを行うには、テンプレートとテンプレートのメタプログラミングに非常に熟練している必要があります。ただし、Dのテンプレートを使用すると、非常に簡単なので、いつでも使用できます。エラーは理解し、処理するのがはるかに簡単です(ただし、エラーは通常、テンプレート化されていない関数で発生するエラーよりもさらに悪いです)。そして、派手なメタプログラミングで言語を強制的に実行させる方法を理解する必要はありません。 。

C++がDが持っているこれらの能力の多くを獲得できなかった理由はありません(C++の概念は、それらが整理される場合に役立ちます)が、テンプレート制約とstatic ifに類似した構成を持つ基本的な条件付きコンパイルを追加するまでC++、C++テンプレートは、使いやすさとパワーの点でDテンプレートと比較できません。

68

Dテンプレートシステムの信じられないほどのパワー(TM)を示すのに適したものは何もないと思います このレンダラー 私は何年も前に見つけました:

The compiler output

はい!これは実際にはコンパイラによって生成されるものです...これは実際に「プログラム」であり、非常にカラフルなプログラムです。

編集する

ソースがオンラインに戻ったようです。

40
bitmask

Dメタプログラミングの最良の例は、C++ BoostおよびSTLモジュールと比較して、Dメタプログラミングを多用するD標準ライブラリモジュールです。 Dの std.rangestd.algorithmstd.functional および std.parallelism を確認してください。これらのどれもC++で実装するのは簡単ではありません。少なくとも、Dモジュールが備えているようなクリーンで表現力豊かなAPIを使用する場合はそうです。

DメタプログラミングであるIMHOを学ぶための最良の方法は、次のような例です。 Andrei Alexandrescu(Dに深く関わったC++テンプレートのメタプログラミングの第一人者)が作成したstd.algorithmとstd.rangeへのコードを読むことで、主に学びました。次に、学んだことを使用してstd.parallelismモジュールに貢献しました。

また、DにはC++ 1xのconstexprと同様のコンパイル時関数評価(CTFE)がありますが、実行時に評価できる関数の大規模で成長するサブセットを変更せずに評価できるという点でより一般的です。コンパイル時間。これはコンパイル時のコード生成に役立ち、生成されたコードは string mixins を使用してコンパイルできます。

27
dsimcha

Dでは、静的な テンプレートパラメータの制約 を簡単に適用して、実際のテンプレート引数に応じて 静的if でコードを記述できます。
テンプレートの特殊化やその他のトリック(boostを参照)を使用して、C++の単純なケースでそれをシミュレートすることは可能ですが、PITAであり、非常に制限されているため、コンパイラーは型に関する多くの詳細を公開しません。

C++で本当にできないことの1つは、高度なコンパイル時コード生成です。

14
Trass3r

これは、カスタムメイドのmap()を実行するDコードで、参照によって結果を返します

長さ4の2つの配列を作成し、maps要素の対応する各ペアを最小値の要素に割り当て、それに50を乗算し、結果を元の配列に保存します

注意すべきいくつかの重要な機能は次のとおりです。

  • テンプレートは可変です:map()は任意の数の引数を取ることができます。

  • コード(比較的)短い!コアロジックであるMapper構造体はわずか15行ですが、それでもわずかな量で多くのことを実行できます。私のポイントは、これはC++では不可能であるということではありませんが、それは確かにコンパクトでクリーンではありません。


import std.metastrings, std.typetuple, std.range, std.stdio;

void main() {
    auto arr1 = [1, 10, 5, 6], arr2 = [3, 9, 80, 4];

    foreach (ref m; map!min(arr1, arr2)[1 .. 3])
        m *= 50;

    writeln(arr1, arr2); // Voila! You get:  [1, 10, 250, 6][3, 450, 80, 4]
}

auto ref min(T...)(ref T values) {
    auto p = &values[0];
    foreach (i, v; values)
        if (v < *p)
            p = &values[i];
    return *p;
}

Mapper!(F, T) map(alias F, T...)(T args) { return Mapper!(F, T)(args); }

struct Mapper(alias F, T...) {
    T src;  // It's a Tuple!

    @property bool empty() { return src[0].empty; }

    @property auto ref front() {
        immutable sources = FormatIota!(q{src[%s].front}, T.length);
        return mixin(Format!(q{F(%s)}, sources));
    }

    void popFront() { foreach (i, x; src) { src[i].popFront(); } }

    auto opSlice(size_t a, size_t b) {
        immutable sliced = FormatIota!(q{src[%s][a .. b]}, T.length);
        return mixin(Format!(q{map!F(%s)}, sliced));
    }
}


// All this does is go through the numbers [0, len),
// and return string 'f' formatted with each integer, all joined with commas
template FormatIota(string f, int len, int i = 0) {
    static if (i + 1 < len)
        enum FormatIota = Format!(f, i) ~ ", " ~ FormatIota!(f, len, i + 1);
    else
        enum FormatIota = Format!(f, i);
}
11
Mehrdad

Dのテンプレート、文字列ミックスイン、およびテンプレートミックスインでの経験を書き留めました。 http://david.rothlis.net/d/templates/

Dで可能なことのフレーバーを提供する必要があります-C++では、識別子を文字列としてアクセスし、コンパイル時にその文字列を変換し、操作された文字列からコードを生成できるとは思いません。

私の結論:非常に柔軟性があり、非常に強力で、単なる常連が使用できますが、より高度なコンパイル時のメタプログラミングに関しては、リファレンスコンパイラは依然としてややバグがあります。

文字列操作、さらには文字列解析。

これはMPライブラリ であり、(多かれ少なかれ)BNFを使用して文字列で定義された文法に基づいて、適切なパーサーを再帰的に生成します。何年も触れていませんが、働いていた。

8
BCS

dでは、型のサイズとその型で使用可能なメソッドを確認し、使用する実装を決定できます

これは、たとえば core.atomicモジュール

bool cas(T,V1,V2)( shared(T)* here, const V1 ifThis, const V2 writeThis ){
    static if(T.sizeof == byte.sizeof){
       //do 1 byte CaS
    }else static if(T.sizeof == short.sizeof){
       //do 2 byte CaS
    }else static if( T.sizeof == int.sizeof ){
       //do 4 byte CaS
    }else static if( T.sizeof == long.sizeof ){
       //do 8 byte CaS
    }else static assert(false);
}
6
ratchet freak

Dレイトレーシングの投稿に対抗するために、ここにC++コンパイル時レイトレーサー( metatrace )を示します。

enter image description here

(ちなみに、それは主にC++ 2003メタプログラミングを使用します。新しいconstexprsでより読みやすくなります)

4
Sebastian Mach

Dのテンプレートメタプログラミングでは、C++ではできないような静かなことがいくつかあります。最も重要なことは、SO苦痛なしでテンプレートメタプログラミングを実行できることです。

1
Ralph Tandetzky