테일 콜 최적화 란 무엇입니까?


817

간단히 말해서 테일 콜 최적화 란 무엇입니까?

더 구체적으로, 왜 적용 할 수 있고, 그렇지 않은 이유에 대한 설명과 함께 작은 코드 스 니펫은 무엇입니까?


10
TCO는 꼬리 위치의 함수 호출을 이동, 점프로 바꿉니다.
Will Ness

8
이 질문은 8 년 전에 완전히 요청되었습니다.)
majelbstoat

답변:


754

테일 콜 최적화는 호출 함수가 단순히 호출 된 함수에서 얻은 값을 반환하기 때문에 함수에 새 스택 프레임을 할당하지 않아도되는 곳입니다. 가장 일반적으로 사용되는 테일 재귀는 테일 콜 최적화를 활용하기 위해 작성된 재귀 함수가 일정한 스택 공간을 사용할 수 있습니다.

Scheme은 구현 시이 최적화를 제공해야한다는 사양을 보장하는 몇 가지 프로그래밍 언어 중 하나입니다 (JavaScript는 ES6부터 시작) . 따라서 Scheme의 계승 함수에 대한 두 가지 예는 다음과 같습니다.

(define (fact x)
  (if (= x 0) 1
      (* x (fact (- x 1)))))

(define (fact x)
  (define (fact-tail x accum)
    (if (= x 0) accum
        (fact-tail (- x 1) (* x accum))))
  (fact-tail x 1))

첫 번째 함수는 재귀 호출이 아닙니다. 왜냐하면 재귀 호출이 이루어질 때 함수는 호출이 반환 된 후 결과와 관련된 곱셈을 추적해야하기 때문입니다. 따라서 스택은 다음과 같습니다.

(fact 3)
(* 3 (fact 2))
(* 3 (* 2 (fact 1)))
(* 3 (* 2 (* 1 (fact 0))))
(* 3 (* 2 (* 1 1)))
(* 3 (* 2 1))
(* 3 2)
6

반대로 꼬리 재귀 요인의 스택 추적은 다음과 같습니다.

(fact 3)
(fact-tail 3 1)
(fact-tail 2 3)
(fact-tail 1 6)
(fact-tail 0 6)
6

보시다시피 우리는 팩트 테일에 대한 모든 호출에 대해 동일한 양의 데이터 만 추적하면됩니다. 왜냐하면 우리는 단순히 우리가 얻는 가치를 맨 위로 돌려주기 때문입니다. 이것은 (사실 1000000)을 호출하더라도 (사실 3)과 동일한 공간 만 필요하다는 것을 의미합니다. 이는 비 꼬리 재귀 사실의 경우에는 해당되지 않으며 값이 크면 스택 오버플로가 발생할 수 있습니다.


99
이것에 대해 더 배우고 싶다면 컴퓨터 프로그램의 구조와 해석의 첫 번째 장을 읽는 것이 좋습니다.
Kyle Cronin

3
큰 대답은 완벽하게 설명했다.
요나

15
엄밀히 말하면, 테일 콜 최적화는 반드시 호출자의 스택 프레임을 피 호출자로 대체 할 필요는 없지만 테일 위치에있는 무제한의 호출이 제한된 양의 공간 만 요구하도록합니다. 참조 윌 클린저의 논문 "적절한 꼬리 재귀 및 공간 효율성" cesura17.net/~will/Professional/Research/Papers/tail.pdf
존 Harrop에

3
이것은 일정한 공간 방식으로 재귀 함수를 작성하는 방법입니까? 반복 접근 방식을 사용하여 동일한 결과를 얻을 수 없기 때문에?
dclowd9901

5
@ dclowd9901에서 TCO를 사용하면 반복 루프보다 기능적 스타일을 선호 할 수 있습니다. 명령형 스타일을 선호 할 수 있습니다. 많은 언어 (Java, Python)는 TCO를 제공하지 않으므로 함수 호출에 메모리 비용이 소요되며 명령형 스타일이 선호된다는 것을 알아야합니다.
mcoolive

551

간단한 예 : C로 구현 된 계승 함수를 살펴 보겠습니다.

우리는 명백한 재귀 적 정의로 시작합니다.

unsigned fac(unsigned n)
{
    if (n < 2) return 1;
    return n * fac(n - 1);
}

함수가 반환되기 전 마지막 작업이 다른 함수 호출 인 경우 함수는 테일 호출로 끝납니다. 이 호출이 동일한 함수를 호출하면 꼬리 재귀입니다.

비록 fac()첫 눈에 보이는 꼬리 재귀 무엇 실제로 일어나는 것은, 그것은 아니다

unsigned fac(unsigned n)
{
    if (n < 2) return 1;
    unsigned acc = fac(n - 1);
    return n * acc;
}

즉, 마지막 연산은 함수 호출이 아닌 곱셈입니다.

그러나 fac()누적 된 값을 콜 체인에 추가 인수로 전달하고 최종 결과 만 반환 값으로 다시 전달하여 테일 재귀 로 다시 작성할 수 있습니다.

unsigned fac(unsigned n)
{
    return fac_tailrec(1, n);
}

unsigned fac_tailrec(unsigned acc, unsigned n)
{
    if (n < 2) return acc;
    return fac_tailrec(n * acc, n - 1);
}

자, 이것이 왜 유용한가요? 꼬리 호출 직후에 돌아 오기 때문에 꼬리 위치에서 함수를 호출하기 전에 이전 스택 프레임을 무시하거나 재귀 함수의 경우 스택 프레임을 그대로 다시 사용할 수 있습니다.

테일 콜 최적화는 재귀 코드를

unsigned fac_tailrec(unsigned acc, unsigned n)
{
TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;
}

이것은 인라인 될 수 있고 fac()우리는 도착

unsigned fac(unsigned n)
{
    unsigned acc = 1;

TOP:
    if (n < 2) return acc;
    acc = n * acc;
    n = n - 1;
    goto TOP;
}

어느 것이

unsigned fac(unsigned n)
{
    unsigned acc = 1;

    for (; n > 1; --n)
        acc *= n;

    return acc;
}

여기서 알 수 있듯이, 충분히 고급화 된 최적화 프로그램은 테일 재귀를 반복으로 대체 할 수 있습니다. 이는 함수 호출 오버 헤드를 피하고 일정한 양의 스택 공간 만 사용하면 훨씬 효율적입니다.


스택 프레임의 의미를 정확하게 설명 할 수 있습니까? 호출 스택과 스택 프레임 사이에 차이가 있습니까?
Shasak

10
@Kasahs : 스택 프레임은 주어진 (활성화 된) 기능과 함께 포함되는 호출 스택의 일부입니다. cf en.wikipedia.org/wiki/Call_stack#Structure
Christoph


198

TCO (Tail Call Optimization)는 스마트 컴파일러가 함수를 호출하고 추가 스택 공간을 차지하지 않는 프로세스입니다. 이 상황이 발생하는 유일한 상황은 함수 f 에서 마지막으로 실행 된 명령어 가 함수 g에 대한 호출 인 경우입니다 (참고 : gf 일 수 있음 ). 여기서 중요한 점은 f 는 더 이상 스택 공간이 필요하지 않다는 것입니다. 단순히 g 를 호출 한 다음 g 가 반환하는 것을 반환합니다. 이 경우 g는 단지 실행되고 f라는 값에 필요한 값을 반환하도록 최적화 할 수 있습니다.

이 최적화는 재귀 호출이 폭발하지 않고 일정한 스택 공간을 차지하게합니다.

예 :이 계승 함수는 TCOptimizable이 아닙니다.

def fact(n):
    if n == 0:
        return 1
    return n * fact(n-1)

이 함수는 return 문에서 다른 함수를 호출하는 것 외에도 작업을 수행합니다.

아래 기능은 TCOptimizable입니다.

def fact_h(n, acc):
    if n == 0:
        return acc
    return fact_h(n-1, acc*n)

def fact(n):
    return fact_h(n, 1)

이러한 함수 중 하나에서 마지막으로 발생하는 것은 다른 함수를 호출하는 것입니다.


3
전체 '함수 g는 f가 될 수 있습니다'라는 것은 다소 혼란 스러웠지만 의미하는 바가 있으며 예제가 실제로 명확 해졌습니다. 고마워요!
majelbstoat

10
개념을 보여주는 훌륭한 예입니다. 선택한 언어가 테일 콜 제거 또는 테일 콜 최적화를 구현해야한다는 점을 고려하십시오. Python으로 작성된 예제에서 값 1000을 입력하면 기본 Python 구현이 Tail Recursion Elimination을 지원하지 않으므로 "RuntimeError : maximum recursion depth exceeded"가 표시됩니다. 그 이유를 설명하는 Guido 자신의 게시물을보십시오 : neopythonic.blogspot.pt/2009/04/tail-recursion-elimination.html .
rmcc

" 유일한 상황"은 너무 절대적입니다. 또한 거기 부 (Trmc)는 최적화 것이다, 적어도 이론적으로, (cons a (foo b))또는 (+ c (bar d))같은 방법으로 꼬리 위치.
Will Ness

나는 내가 수학 사람이기 때문에 받아 들여진 대답보다 더 나은 f와 g 접근 방식을 좋아했습니다.
Nithin

나는 당신이 TCOptimized를 의미한다고 생각합니다. TCOptimizable이 아니라고 말하면 최적화 할 수 없다고 추측 할 수 있습니다 (실제로는 가능할 때)
Jacques Mathieu

65

아마도 꼬리 호출, 재귀 꼬리 호출 및 꼬리 호출 최적화에 대해 찾은 최고의 높은 수준의 설명은 블로그 게시물입니다.

"도대체 무엇입니까 : 꼬리 전화"

댄 수갈 스키 테일 콜 최적화에서 그는 다음과 같이 씁니다.

잠시 동안이 간단한 기능을 고려하십시오.

sub foo (int a) {
  a += 15;
  return bar(a);
}

그렇다면 언어 컴파일러가 무엇을 할 수 있습니까? 그것이 할 수있는 일은 폼의 코드를 return somefunc();낮은 수준의 시퀀스로 바꾸는 것 pop stack frame; goto somefunc();입니다. 우리의 예에서 우리가 부르는 그 수단 전에 bar, foo자신을 정리하고, 오히려 전화보다 bar서브 루틴으로, 우리는 낮은 수준의 수행 goto의 시작 동작을 bar. Foo'이미 너무 때, 스택의 자체를 청소 s의 bar보이는이 좋아요라는 누구든지 foo정말 촉구했다 bar때, 그리고 bar그 값을 반환, 그것은 직접 호출 누구에 반환 foo오히려 그것을 돌려보다, foo다음 호출자에게 반환한다.

그리고 꼬리 재귀에 :

마지막 재귀로 함수가 자신을 호출 한 결과를 반환하면 꼬리 재귀가 발생 합니다 . 꼬리 재귀는 어딘가 임의의 함수의 시작 부분으로 건너 뛰기보다는 자신의 시작 부분으로 돌아가는 것이므로 다루기가 더 쉽습니다.

그래서이 :

sub foo (int a, int b) {
  if (b == 1) {
    return a;
  } else {
    return foo(a*a + a, b - 1);
  }

조용히 :

sub foo (int a, int b) {
  label:
    if (b == 1) {
      return a;
    } else {
      a = a*a + a;
      b = b - 1;
      goto label;
   }

이 설명에서 내가 좋아하는 것은 명령형 언어 배경 (C, C ++, Java)에서 온 사람들을 간결하고 쉽게 이해하는 것입니다.


4
404 오류 그러나 여전히 archive.org에서 사용할 수 있습니다. web.archive.org/web/20111030134120/http://www.sidhe.org/~dan/…
Tommy

나는 그것을 얻지 못했습니다. 초기 foo함수 꼬리 호출이 최적화되지 않습니까? 함수를 마지막 단계로만 호출하고 있으며 단순히 해당 값을 반환하는 것입니까?
SexyBeast

1
@TryinHard는 아마도 당신이 생각한 것이 아닐 수도 있지만, 그것이 무엇인지에 대한 요지를 제공하기 위해 그것을 업데이트했습니다. 미안, 전체 기사를 반복하지 않을 것입니다!
btiernay

2
감사합니다. 이것은 가장 투표가
잘된

2
기능적인 언어로 거의 뛰어 들지 않는 사람으로서 "나의 방언"에 대한 설명을 보게되어 기쁩니다. 기능 프로그래머가 자신이 선택한 언어로 복음을 전하는 경향은 (이해할 수있는) 경향이 있지만, 명령형 세계에서 왔을 때 나는 이와 같은 대답에 머리를 감는 것이 훨씬 쉽다는 것을 알게되었습니다.
James Beninger

15

우선 모든 언어가 지원하는 것은 아닙니다.

TCO는 특별한 재귀 사례에 적용됩니다. 요점은 함수에서 마지막으로 수행하는 작업이 자체 호출 (예 : "꼬리"위치에서 자체 호출) 인 경우 표준 재귀 대신 반복처럼 작동하도록 컴파일러에서 최적화 할 수 있다는 것입니다.

일반적으로 재귀 중에 런타임은 모든 재귀 호출을 추적해야하므로 하나가 반환되면 이전 호출에서 다시 시작할 수 있습니다. (재귀 호출 결과를 수동으로 작성하여 이것이 어떻게 작동하는지 시각적으로 알 수 있습니다.) 모든 호출을 추적하면 공간이 많이 차지되므로 함수 자체를 많이 호출 할 때 공간이 많이 차지합니다. 그러나 TCO를 사용하면 "처음으로 돌아가서 이번에 만 매개 변수 값을 새로운 값으로 변경합니다"라고 말할 수 있습니다. 재귀 호출 후 해당 값을 참조하는 것이 없기 때문에 그렇게 할 수 있습니다.


3
테일 호출은 비 재귀 기능에도 적용될 수 있습니다. 리턴하기 전에 마지막 계산이 다른 함수에 대한 호출 인 함수는 테일 호출을 사용할 수 있습니다.
브라이언

언어별로 언어가 반드시 사실 일 필요는 없습니다. 64 비트 C # 컴파일러는 꼬리 opcode를 삽입 할 수 있지만 32 비트 버전은 그렇지 않습니다. F # 릴리스 빌드는되지만 F # 디버그는 기본적으로 작동하지 않습니다.
Steve Gilham

3
"TCO는 재귀의 특별한 경우에 적용됩니다." 나는 그것이 완전히 잘못된 것을 두려워합니다. 테일 통화는 테일 위치의 모든 통화에 적용됩니다. 재귀와 관련하여 일반적으로 논의되지만 실제로는 재귀와 관련이 없습니다.
Jon Harrop

@Brian, 위에 제공된 @btiernay 링크를보십시오. 초기 foo메소드 테일 호출이 최적화되지 않습니까?
SexyBeast

13

x86 분해 분석을 통한 GCC 최소 실행 가능 예

생성 된 어셈블리를보고 GCC가 어떻게 자동으로 테일 콜 최적화를 수행 할 수 있는지 봅시다.

이것은 최적화가 재귀 함수 호출을 루프로 변환 할 수 있다는 https://stackoverflow.com/a/9814654/895245 와 같은 다른 답변에서 언급 된 것의 매우 구체적인 예입니다 .

메모리 액세스는 종종 오늘날 프로그램 속도를 늦추는 주된 요소 이므로 메모리를 절약하고 성능을 향상시킵니다 .

입력으로 GCC에 최적화되지 않은 순진 스택 기반 계승을 제공합니다.

tail_call.c

#include <stdio.h>
#include <stdlib.h>

unsigned factorial(unsigned n) {
    if (n == 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main(int argc, char **argv) {
    int input;
    if (argc > 1) {
        input = strtoul(argv[1], NULL, 0);
    } else {
        input = 5;
    }
    printf("%u\n", factorial(input));
    return EXIT_SUCCESS;
}

GitHub의 상류 .

컴파일 및 분해 :

gcc -O1 -foptimize-sibling-calls -ggdb3 -std=c99 -Wall -Wextra -Wpedantic \
  -o tail_call.out tail_call.c
objdump -d tail_call.out

여기서 -foptimize-sibling-calls꼬리 호출의 일반화 이름은 man gcc다음과 같습니다.

   -foptimize-sibling-calls
       Optimize sibling and tail recursive calls.

       Enabled at levels -O2, -O3, -Os.

에서 언급 한 바와 같이 : GCC는 꼬리 재귀 최적화를 수행하고 있는지 어떻게 확인합니까?

나는 다음과 같은 -O1이유로 선택합니다 .

  • 로 최적화가 수행되지 않습니다 -O0. 필요한 중간 변환이 없기 때문에 이것이 의심됩니다.
  • -O3 꼬리 호출에 최적화되어 있지만 교육적이지 않은 불경건 한 코드를 생성합니다.

분해 -fno-optimize-sibling-calls:

0000000000001145 <factorial>:
    1145:       89 f8                   mov    %edi,%eax
    1147:       83 ff 01                cmp    $0x1,%edi
    114a:       74 10                   je     115c <factorial+0x17>
    114c:       53                      push   %rbx
    114d:       89 fb                   mov    %edi,%ebx
    114f:       8d 7f ff                lea    -0x1(%rdi),%edi
    1152:       e8 ee ff ff ff          callq  1145 <factorial>
    1157:       0f af c3                imul   %ebx,%eax
    115a:       5b                      pop    %rbx
    115b:       c3                      retq
    115c:       c3                      retq

-foptimize-sibling-calls:

0000000000001145 <factorial>:
    1145:       b8 01 00 00 00          mov    $0x1,%eax
    114a:       83 ff 01                cmp    $0x1,%edi
    114d:       74 0e                   je     115d <factorial+0x18>
    114f:       8d 57 ff                lea    -0x1(%rdi),%edx
    1152:       0f af c7                imul   %edi,%eax
    1155:       89 d7                   mov    %edx,%edi
    1157:       83 fa 01                cmp    $0x1,%edx
    115a:       75 f3                   jne    114f <factorial+0xa>
    115c:       c3                      retq
    115d:       89 f8                   mov    %edi,%eax
    115f:       c3                      retq

이 둘의 주요 차이점은 다음과 같습니다.

  • -fno-optimize-sibling-calls사용하는 callq일반적인 비 최적화 함수 호출이다.

    이 명령어는 반환 주소를 스택으로 푸시하여 증가시킵니다.

    또한,이 버전 수행 push %rbx되는 푸시 %rbx스택에 .

    GCC는 edi첫 번째 함수 인수 ( n)를 저장 ebx한 다음 호출 하기 때문에이 작업 을 수행합니다 factorial.

    GCC factorial는 새 호출을 사용할 다른 호출을 준비 중이므로이 작업을 수행해야합니다 edi == n-1.

    ebx이 레지스터는 수신자가 저장되기 때문에 선택됩니다 : 어떤 레지스터가 리눅스 x86-64 함수 호출을 통해 유지 되므로 서브 factorial변경되지 않고 잃게됩니다 n.

  • -foptimize-sibling-calls스택으로 푸시되는 명령어를 사용하지 않습니다 . 명령어 및 로만 goto점프합니다 .factorialjejne

    따라서이 버전은 함수 호출없이 while 루프와 동일합니다. 스택 사용량은 일정합니다.

우분투 18.10, GCC 8.2에서 테스트되었습니다.



3
  1. 함수 자체에 goto 문이 없어야합니다. .. 함수 호출에 의해 호출 수신자 함수의 마지막 항목이됩니다.

  2. 대규모 재귀에서는이를 최적화에 사용할 수 있지만 소규모에서는 함수 호출을 꼬리 호출로 만들기위한 명령 오버 헤드가 실제 목적을 줄입니다.

  3. TCO는 영원히 실행되는 기능을 일으킬 수 있습니다.

    void eternity()
    {
        eternity();
    }
    

3은 아직 최적화되지 않았습니다. 이는 컴파일러가 재귀 코드 대신 일정한 스택 공간을 사용하는 반복 코드로 변환되는 최적화되지 않은 표현입니다. TCO는 데이터 구조에 잘못된 재귀 체계를 사용하는 원인이 아닙니다.
nomen

"TCO는 데이터 구조에 잘못된 재귀 체계를 사용하는 원인이 아닙니다."주어진 사례와 관련이있는 방법을 자세히 설명하십시오. 위의 예는 TCO가 있거나없는 콜 스택에 할당 된 프레임의 예를 나타냅니다.
grillSandwich

근거없는 재귀를 사용하여 트래버스 ()를 선택했습니다. 그것은 TCO와 아무 관련이 없습니다. 영원은 꼬리 호출 위치이지만 꼬리 호출 위치는 필요하지 않습니다. void eternity () {eternity (); 출구(); }
nomen

우리가 그 동안 "대규모 재귀"는 무엇입니까? 함수에서 goto를 피해야하는 이유는 무엇입니까? 이것은 TCO를 허용하는 데 필요하거나 충분하지 않습니다. 그리고 어떤 명령 오버 헤드가 있습니까? TCO의 핵심은 컴파일러가 꼬리 위치에서 함수 호출을 goto로 대체한다는 것입니다.
nomen

TCO는 콜 스택에 사용되는 공간을 최적화하는 것입니다. 대규모 재귀에서는 프레임 크기를 말합니다. 재귀가 발생할 때마다 호출 수신자 함수 위에 콜 스택에 거대한 프레임을 할당 해야하는 경우 TCO가 더 도움이되고 더 많은 재귀 수준을 허용합니다. 그러나 내 프레임 크기가 작은 경우 TCO 없이도 프로그램을 잘 실행할 수 있습니다 (무한한 재귀에 대해서는 이야기하고 있지 않습니다). 기능에 goto가 남아 있으면 "tail"호출은 실제로 tail 호출이 아니며 TCO는 적용되지 않습니다.
grillSandwich

3

재귀 함수 접근 방식에 문제가 있습니다. 크기 O (n)의 호출 스택을 빌드하여 총 메모리 비용 O (n)을 만듭니다. 이로 인해 호출 스택이 너무 커지고 공간이 부족한 스택 오버플로 오류에 취약 해집니다.

테일 콜 최적화 (TCO) 체계. 높은 호출 스택을 작성하지 않기 위해 재귀 함수를 최적화 할 수있는 경우 메모리 비용이 절약됩니다.

(JavaScript, Ruby 및 C와 같은) TCO를 수행하는 많은 언어가 있지만 Python 및 Java는 TCO를 수행하지 않습니다.

JavaScript는 다음을 사용하여 확인했습니다 :) http://2ality.com/2015/06/tail-call-optimization.html


0

함수형 언어에서 꼬리 호출 최적화는 함수 호출이 부분적으로 평가 된 표현식을 결과로 리턴 할 수있는 것처럼 호출자에 의해 평가됩니다.

f x = g x

f 6은 g 6으로 감소합니다. 따라서 구현에서 g 6을 결과로 반환 한 다음 해당 식을 호출하면 스택 프레임이 저장됩니다.

또한

f x = if c x then g x else h x.

f 6을 g 6 또는 h 6으로 줄입니다. 따라서 구현이 c 6을 평가하고 그것이 사실임을 알게되면,

if true then g x else h x ---> g x

f x ---> h x

간단한 비 꼬리 호출 최적화 해석기는 다음과 같습니다.

class simple_expresion
{
    ...
public:
    virtual ximple_value *DoEvaluate() const = 0;
};

class simple_value
{
    ...
};

class simple_function : public simple_expresion
{
    ...
private:
    simple_expresion *m_Function;
    simple_expresion *m_Parameter;

public:
    virtual simple_value *DoEvaluate() const
    {
        vector<simple_expresion *> parameterList;
        parameterList->push_back(m_Parameter);
        return m_Function->Call(parameterList);
    }
};

class simple_if : public simple_function
{
private:
    simple_expresion *m_Condition;
    simple_expresion *m_Positive;
    simple_expresion *m_Negative;

public:
    simple_value *DoEvaluate() const
    {
        if (m_Condition.DoEvaluate()->IsTrue())
        {
            return m_Positive.DoEvaluate();
        }
        else
        {
            return m_Negative.DoEvaluate();
        }
    }
}

테일 콜 최적화 인터프리터는 다음과 같습니다.

class tco_expresion
{
    ...
public:
    virtual tco_expresion *DoEvaluate() const = 0;
    virtual bool IsValue()
    {
        return false;
    }
};

class tco_value
{
    ...
public:
    virtual bool IsValue()
    {
        return true;
    }
};

class tco_function : public tco_expresion
{
    ...
private:
    tco_expresion *m_Function;
    tco_expresion *m_Parameter;

public:
    virtual tco_expression *DoEvaluate() const
    {
        vector< tco_expression *> parameterList;
        tco_expression *function = const_cast<SNI_Function *>(this);
        while (!function->IsValue())
        {
            function = function->DoCall(parameterList);
        }
        return function;
    }

    tco_expresion *DoCall(vector<tco_expresion *> &p_ParameterList)
    {
        p_ParameterList.push_back(m_Parameter);
        return m_Function;
    }
};

class tco_if : public tco_function
{
private:
    tco_expresion *m_Condition;
    tco_expresion *m_Positive;
    tco_expresion *m_Negative;

    tco_expresion *DoEvaluate() const
    {
        if (m_Condition.DoEvaluate()->IsTrue())
        {
            return m_Positive;
        }
        else
        {
            return m_Negative;
        }
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.