programing

C ++ 및 D의 메타 프로그래밍

nasanasas 2020. 11. 16. 21:35
반응형

C ++ 및 D의 메타 프로그래밍


C ++의 템플릿 메커니즘은 우연히 템플릿 메타 프로그래밍에 유용하게되었습니다. 반면에 D는이를 용이하게하기 위해 특별히 설계되었습니다. 그리고 분명히 이해하기가 더 쉽습니다 (또는 들었습니다).

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!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(경우 일 것 같은 T에서 파생 된 U), 다음 func의 경우 반면, 컴파일 T을 암시 적으로 변환되지 않습니다 U, 그것은하지 않습니다. 이러한 간단한 개선으로 인해 기본 템플릿 전문화도 훨씬 더 강력 해집니다 (템플릿 제약 조건 없이도 또는 static if).

개인적으로 저는 컨테이너와 가끔씩 사용하는 함수 외에는 C ++에서 템플릿을 거의 사용하지 않습니다 <algorithm>. 왜냐하면 그것들은 사용하기가 너무 고통스럽기 때문입니다. 추악한 오류가 발생하고 멋진 작업을 수행하기가 매우 어렵습니다. 조금이라도 복잡한 작업을 수행하려면 템플릿과 템플릿 메타 프로그래밍에 능숙해야합니다. 그러나 D의 템플릿을 사용하면 항상 사용하기가 쉽습니다. 오류는 이해하고 처리하기가 훨씬 쉬우 며 (일반적으로 템플릿이없는 함수에서 발생하는 오류보다 여전히 더 나쁘지만) 멋진 메타 프로그래밍으로 언어가 원하는 작업을 수행하도록 강제하는 방법을 알아낼 필요가 없습니다. .

C ++가 D가 가진 이러한 능력을 많이 얻지 못할 이유는 없습니다 (C ++ 개념이 이러한 기능을 분류하면 도움이 될 것입니다).하지만 템플릿 제약 조건과 유사한 구조로 기본 조건부 컴파일을 추가하기 전까지 static if는 C ++, C ++ 템플릿 만 사용 편의성과 성능 측면에서 D 템플릿과 비교할 수 없습니다.


몇 년 전에 찾은 이 렌더러 보다 D 템플릿 시스템의 놀라운 성능 (TM)을 더 잘 보여줄 수있는 것은 없다고 생각합니다 .

컴파일러 출력

예! 이것은 실제로 컴파일러에 의해 생성 된 것입니다. 이것은 "프로그램"이고 실제로는 매우 다채로운 것입니다.

편집하다

소스가 다시 온라인 상태 인 것 같습니다.


D 메타 프로그래밍의 가장 좋은 예는 C ++ Boost 및 STL 모듈과 비교하여이를 많이 사용하는 D 표준 라이브러리 모듈입니다. D의 std.range , std.algorithm , std.functionalstd.parallelism을 확인하십시오 . 이들 중 어느 것도 C ++에서 구현하기 쉽지 않을 것입니다. 적어도 D 모듈이 가지고있는 깔끔하고 표현력있는 API를 사용한다면 말입니다.

D 메타 프로그래밍 IMHO를 배우는 가장 좋은 방법은 이러한 종류의 예입니다. 나는 Andrei Alexandrescu (D에 깊이 관여 한 C ++ 템플릿 메타 프로그래밍 전문가)가 작성한 std.algorithm 및 std.range에 대한 코드를 읽음으로써 크게 배웠습니다. 그런 다음 내가 배운 것을 사용하고 std.parallelism 모듈에 기여했습니다.

또한 D에는 C ++ 1x와 유사 constexpr하지만 런타임에 평가할 수있는 크고 증가하는 함수 하위 집합이 컴파일 타임에 수정되지 않은 상태로 평가 될 수 있다는 점에서 훨씬 더 일반적인 컴파일 타임 함수 평가 (CTFE) 가 있습니다. 이것은 컴파일 타임 코드 생성에 유용하며 생성 된 코드는 string mixins를 사용하여 컴파일 할 수 있습니다 .


그럼 D에 쉽게 정적 부과 할 수 템플릿 매개 변수에 제약을 가진 실제 템플릿 인수에 따라 및 쓰기 코드 정적 경우 .
템플릿 전문화 및 기타 트릭 (부스트 참조)을 사용하여 C ++로 간단한 경우를 시뮬레이션 할 수 있지만 이는 PITA이며 컴파일러가 유형에 대한 많은 세부 정보를 노출하지 않는 매우 제한적인 원인입니다.

C ++가 실제로 할 수없는 한 가지는 정교한 컴파일 타임 코드 생성입니다.


다음 은 참조로 결과map()반환 하는 맞춤 제작을 수행하는 D 코드입니다 .

길이가 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 ++에서는 식별자에 문자열로 액세스하고 컴파일 타임에 해당 문자열을 변환하고 조작 된 문자열에서 코드를 생성 할 수 있다고 생각하지 않습니다.

내 결론 : 매우 유연하고 매우 강력하며 단순한 필사자가 사용할 수 있지만 참조 컴파일러는 더 진보 된 컴파일 타임 메타 프로그래밍에 관해서는 여전히 다소 버그가 있습니다.


String manipulation, even string parsing.

This is a MP library that generates recursive decent parsers based on grammars defined in strings using (more or less) BNF. I haven't touched it in years but it used to work.


in D you can check the size of a type and the available methods on it and decide which implementation you want to use

this is used for example in the core.atomic module

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

Just to counter the D ray tracing post, here is a C++ compile time ray tracer (metatrace):

여기에 이미지 설명 입력

(by the way, it uses mostly C++2003 metaprogramming; it would be more readable with the new constexprs)


There are quiet a few things you can do in template metaprogramming in D that you cannot do in C++. The most important thing is that you can do template metaprogramming WITHOUT SO MUCH OF A PAIN!

참고 URL : https://stackoverflow.com/questions/7300298/metaprogramming-in-c-and-in-d

반응형