簡體   English   中英

C++ 和 D 中的元編程

[英]Metaprogramming in C++ and in D

C++ 中的模板機制只是偶然地對模板元編程有用。 另一方面,D's 是專門為促進這一點而設計的。 顯然它更容易理解(或者我聽說過)。

我沒有使用 D 的經驗,但我很好奇,在模板元編程方面,您可以在 D 中做什么而在 C++ 中不能做什么?

在 D 中幫助模板元編程的兩個最重要的事情是模板約束和static if - C++ 理論上可以添加這兩者,並且會從中受益匪淺。

模板約束允許您在模板上放置一個條件,該條件必須為真,模板才能被實例化。 例如,這是std.algorithm.find重載之一的簽名:

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

為了能夠實例化這個模板化函數,類型R必須是由std.range.isInputRange定義的輸入范圍(所以isInputRange!R必須是true ),並且給定的謂詞需要是一個二元函數使用給定的參數進行編譯並返回一個可隱式轉換為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++ 中進行一些非常復雜的模板元編程才能嘗試這樣的事情,而您的普通程序員不會反正能看懂,更不用說自己動手了。 Boost 就是一個典型的例子。 它做了一些了不起的事情,但它非常復雜。

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 (就像T派生自U ),則func將編譯,而如果T不可隱式轉換為U ,則不會。 這種簡單的改進甚至使基本模板特化變得更加強大(即使沒有模板約束或static if )。

就我個人而言,除了容器和<algorithm>的偶爾函數之外,我很少在 C++ 中使用模板,因為它們使用起來非常痛苦。 它們會導致丑陋的錯誤,並且很難做任何花哨的事情。 要做任何稍微復雜的事情,您都需要非常熟練地使用模板和模板元編程。 但是使用 D 中的模板,它非常容易,我一直使用它們。 這些錯誤更容易理解和處理(盡管它們仍然比非模板化函數的錯誤更糟糕),而且我不必弄清楚如何通過花哨的元編程來強制語言做我想做的事.

C++ 沒有理由不能獲得 D 擁有的這些能力中的很多(C++ 概念如果能夠解決這些問題會有所幫助),但是直到他們添加具有類似於模板約束和static if構造的基本條件編譯到 C++、C++模板只是在易用性和功能方面無法與 D 模板進行比較。

我相信沒有什么比我多年前發現的這個渲染器更能展示 D 模板系統令人難以置信的力量 (TM) 了:

編譯器輸出

是的! 這實際上是由編譯器生成的……它是“程序”,確實是一個非常豐富多彩的程序。

編輯

來源似乎重新上線。

D 元編程的最佳示例是大量使用它的 D 標准庫模塊,而不是 C++ Boost 和 STL 模塊。 查看 D 的std.rangestd.algorithmstd.functionalstd.parallelism 這些都不容易在 C++ 中實現,至少使用 D 模塊具有的那種干凈、富有表現力的 API。

學習 D 元編程的最好方法,恕我直言,就是通過這些例子。 我主要是通過閱讀 Std.algorithm 和 std.range 的代碼來學習的,這些代碼由 Andrei Alexandrescu(一位 C++ 模板元編程大師,與 D 密切相關)編寫。 然后我使用了我學到的知識並貢獻了 std.parallelism 模塊。

另請注意,D 具有編譯時函數求值 (CTFE),它類似於 C++1x 的constexpr但更通用,因為可以在運行時求值的大量且不斷增長的函數子集可以在編譯時不加修改地求值。 這對於編譯時代碼生成很有用,生成的代碼可以使用字符串 mixins進行編譯。

好吧,在 D 中,您可以輕松地對模板參數施加靜態約束,並根據使用static if的實際模板參數編寫代碼。
可以通過使用模板特化和其他技巧(參見 boost)模擬 C++ 的簡單情況,但它是一個 PITA 並且非常有限,因為編譯器不會公開有關類型的許多細節。

C++ 真正不能做的一件事是復雜的編譯時代碼生成。

這是一段 D 代碼,它執行定制的map()它通過引用返回其結果

它創建兩個長度為 4 的數組,每對對應的元素映射到具有最小值的元素,並將其乘以 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);
}

我寫了我在 D 的模板、字符串混合和模板混合方面的經驗: http : //david.rothlis.net/d/templates/

它應該讓您了解 D 中的可能性——我不認為在 C++ 中您可以將標識符作為字符串訪問,在編譯時轉換該字符串,並從操作的字符串生成代碼。

我的結論:非常靈活,非常強大,普通人也可以使用,但是當涉及到更高級的編譯時元編程內容時,參考編譯器仍然有些錯誤。

字符串操作,甚至字符串解析。

這是一個 MP 庫,它基於使用(或多或少)BNF 的字符串中定義的語法生成遞歸體面的解析器。 我已經好幾年沒碰它了,但它曾經奏效。

在 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);
}

只是為了對抗 D 射線追蹤帖子,這里是一個 C++ 編譯時射線追蹤器( metatrace ):

在此處輸入圖片說明

(順便說一下,它主要使用 C++2003 元編程;使用新的constexpr會更易讀)

在 D 中的模板元編程中,您可以做一些在 C++ 中無法做的事情。 最重要的是,您可以輕松地進行模板元編程!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM