Lambda 자체 복귀 : 합법적입니까?


124

이 상당히 쓸모없는 프로그램을 고려하십시오.

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

기본적으로 우리는 스스로를 반환하는 람다를 만들려고합니다.

  • MSVC가 프로그램을 컴파일하고 실행합니다.
  • gcc는 프로그램을 컴파일하고 segfaults
  • clang은 다음 메시지와 함께 프로그램을 거부합니다.

    error: function 'operator()<(lambda at lam.cpp:6:13)>' with deduced return type cannot be used before it is defined

어떤 컴파일러가 옳습니까? 정적 제약 조건 위반, UB 또는 둘 다 있습니까?

업데이트되는 이 약간의 수정은 그 소리에 의해 허용됩니다 :

  auto it = [&](auto& self, auto b) {
          std::cout << (a + b) << std::endl;
          return [&](auto p) { return self(self,p); };
  };
  it(it,4)(6)(42)(77)(999);

업데이트 2 : 자신을 반환하는 펑터를 작성하는 방법 또는이를 달성하기 위해 Y 조합기를 사용하는 방법을 이해합니다. 이것은 언어 변호사 질문에 가깝습니다.

3 업데이트 : 질문은 하지 람다는 일반적으로 그 자체를 반환하는 것이 합법적인지,하지만이 일이 특정 방법의 적법성에 대해.

관련 질문 : C ++ lambda returning self .


2
clang은이 순간에 더보기 좋게 보입니다. 그런 구조가 타입 검사를 할 수 있는지 궁금합니다. 무한한 트리로 끝날 가능성이 더 큽니다.
bipll

2
정말 그 접근을하지 않는이 언어 변호사의 질문이다라고하는 법적하지만 답변의 몇 가지 경우 귀하가 질문 ... 바로 태그를하는 것이 중요합니다
Shafik Yaghmour

2
@ShafikYaghmour 감사
합니다.

1
@ArneVogel 예 업데이트 된 auto& self참조 문제를 제거하는 사용합니다 .
n. '대명사'm.

1
@TheGreatDuck C ++ 람다는 실제로 이론적 인 람다식이 아닙니다. C ++에는 원래 단순 유형의 람다 미적분으로 표현할 수없는 기본 제공 재귀 유형 이 있으므로 a : a-> a 및 기타 불가능한 구성과 동형을 가질 수 있습니다.
n. '대명사'm.

답변:


68

프로그램은 [dcl.spec.auto] / 9에 따라 형식이 잘못되었습니다 (clang이 맞습니다) .

정의되지 않은 자리 표시 자 유형의 엔터티 이름이 식에 나타나면 프로그램의 형식이 잘못된 것입니다. 그러나 폐기되지 않은 return 문이 함수에서 발견되면 해당 문에서 추론 된 반환 유형을 다른 return 문을 포함하여 나머지 함수에서 사용할 수 있습니다.

기본적으로 내부 람다의 반환 유형의 추론은 자체에 따라 달라집니다 (여기에서 명명 된 엔티티는 호출 연산자입니다). 따라서 반환 유형을 명시 적으로 제공해야합니다. 이 특별한 경우에는 내부 람다 유형이 필요하지만 이름을 지정할 수 없기 때문에 불가능합니다. 그러나 이와 같이 재귀 적 람다를 강제로 사용하려는 다른 경우가 있습니다.

그것 없이도 매달린 참조가 있습니다.


좀 더 똑똑한 사람 (예 : TC)과 논의한 후 좀 더 자세히 설명하겠습니다. 원본 코드 (약간 축소)와 제안 된 새 버전 (약간 축소) 사이에는 중요한 차이가 있습니다.

auto f1 = [&](auto& self) {
  return [&](auto) { return self(self); } /* #1 */ ; /* #2 */
};
f1(f1)(0);

auto f2 = [&](auto& self, auto) {
  return [&](auto p) { return self(self,p); };
};
f2(f2, 0);

그리고 그 내부 표현이다 self(self)에 의존하지 않는 f1, 그러나 self(self, p)에 따라 달라집니다 f2. 표현식이 비의존적일 때, 그들은 열심히 사용할 수 있습니다 ... ( [temp.res] / 8 , 예를 들어 static_assert(false)템플릿이 인스턴스화되었는지 여부에 관계없이 하드 오류는 어떻습니까 ).

의 경우 f1컴파일러 ( 예 : clang)가이를 열심히 인스턴스화 할 수 있습니다. 위의 ;지점 #2(내부 람다 유형)에 도달하면 외부 람다의 추론 된 유형을 알고 있지만, 그보다 더 일찍 사용하려고합니다 (at point로 생각 #1)-우리는 시도하고 있습니다 실제로 유형이 무엇인지 알기 전에 내부 람다를 구문 분석하는 동안 사용합니다. 그것은 dcl.spec.auto/9를 위반합니다.

그러나 f2의 경우 종속적이기 때문에 열심히 인스턴스화 할 수 없습니다. 우리는 모든 것을 알고있는 사용 시점에서만 인스턴스화 할 수 있습니다.


실제로 이와 같은 작업을 수행하려면 y-combinator 가 필요합니다 . 논문의 구현 :

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

그리고 당신이 원하는 것은 :

auto it = y_combinator([&](auto self, auto b){
    std::cout << (a + b) << std::endl;
    return self;
});

반환 유형을 명시 적으로 지정하는 방법은 무엇입니까? 나는 그것을 이해할 수 없다.
Rakete1111

@ Rakete1111 어느 쪽? 원본에서는 할 수 없습니다.
Barry

오 그래. 나는 네이티브가 아니지만 "그래서 당신은 반환 유형을 명시 적으로 제공해야합니다"는 방법이 있음을 암시하는 것 같습니다. 그래서 내가 묻고 있었던 것입니다 :)
Rakete1111

4
@PedroA stackoverflow.com/users/2756719/tc 는 C ++ 기여자입니다. 그는 또한 AI 가 아니 거나 C ++에 대해 잘 아는 사람이 시카고에서 열린 최근 LWG 미니 회의에 참석하도록 설득 할 수있을만큼 충분한 수완이 있습니다.
Casey

3
@Casey 아니면 인간은 AI가 그에게 말한 것을 그냥 패러 트하고있을 수도 있습니다 ... 당신은 절대 모릅니다;)
TC

34

편집 : 이 구조가 C ++ 사양에 따라 엄격하게 유효한 지에 대한 논란이있는 것 같습니다. 우세한 의견은 그것이 타당하지 않다는 것 같습니다. 더 자세한 토론은 다른 답변을 참조하십시오. 이 답변의 나머지 부분은 구성이 유효한 경우 적용됩니다 . 아래의 조정 된 코드는 MSVC ++ 및 gcc에서 작동하며 OP는 clang에서도 작동하는 추가 수정 된 코드를 게시했습니다.

내부 람다는 self참조로 매개 변수 를 캡처 하지만 온라인 7 self이후 범위를 벗어나기 때문에 정의되지 않은 동작입니다 return. 따라서 반환 된 람다가 나중에 실행될 때 범위를 벗어난 변수에 대한 참조에 액세스합니다.

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self); // <-- using reference to 'self'
      };
  };
  it(it)(4)(6)(42)(77)(999); // <-- 'self' is now out of scope
}

다음과 같이 프로그램을 실행하면 다음과 valgrind같습니다.

==5485== Memcheck, a memory error detector
==5485== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5485== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5485== Command: ./test
==5485== 
9
==5485== Use of uninitialised value of size 8
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485== 
==5485== Invalid read of size 4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  Address 0x4fefffdc4 is not stack'd, malloc'd or (recently) free'd
==5485== 
==5485== 
==5485== Process terminating with default action of signal 11 (SIGSEGV)
==5485==  Access not within mapped region at address 0x4FEFFFDC4
==5485==    at 0x108A20: _ZZZ4mainENKUlT_E_clIS0_EEDaS_ENKUlS_E_clIiEEDaS_ (test.cpp:8)
==5485==    by 0x108AD8: main (test.cpp:12)
==5485==  If you believe this happened as a result of a stack
==5485==  overflow in your program's main thread (unlikely but
==5485==  possible), you can try to increase the size of the
==5485==  main thread stack using the --main-stacksize= flag.
==5485==  The main thread stack size used in this run was 8388608.

대신 외부 람다를 값 대신 참조로 가져 오도록 변경하여 불필요한 복사본을 피하고 문제를 해결할 수도 있습니다.

#include <iostream>
int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto& self) { // <-- self is now a reference
      return [&](auto b) {
        std::cout << (a + b) << std::endl;
        return self(self);
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

이것은 작동합니다 :

==5492== Memcheck, a memory error detector
==5492== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5492== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5492== Command: ./test
==5492== 
9
11
47
82
1004

일반 람다에 익숙하지 않지만 self참조 할 수 없습니까?
François Andrieux

당신이 만드는 경우 @ FrançoisAndrieux 예, self참조를,이 문제는 도망 간다 , 그러나 연타는 여전히 다른 이유로 거부
저스틴

@ FrançoisAndrieux Indeed 그리고 나는 그것을 대답에 추가했습니다. 감사합니다!
TypeIA

이 접근법의 문제점은 가능한 컴파일러 버그를 제거하지 못한다는 것입니다. 따라서 작동해야하지만 구현이 손상되었습니다.
Shafik Yaghmour

감사합니다. 몇 시간 동안 봤는데 참조 self로 캡처 된 것을 보지 못했습니다 !
n. '대명사'm.

21

TL; DR;

clang이 정확합니다.

이것을 잘못된 형식으로 만드는 표준 섹션이 [dcl.spec.auto] p9 인 것처럼 보입니다 .

정의되지 않은 자리 표시 자 유형의 엔터티 이름이 식에 나타나면 프로그램의 형식이 잘못된 것입니다. 그러나 폐기되지 않은 return 문이 함수에서 발견되면 해당 문에서 추론 된 반환 유형을 다른 return 문을 포함하여 나머지 함수에서 사용할 수 있습니다. [ 예:

auto n = n; // error, n’s initializer refers to n
auto f();
void g() { &f; } // error, f’s return type is unknown

auto sum(int i) {
  if (i == 1)
    return i; // sum’s return type is int
  else
    return sum(i-1)+i; // OK, sum’s return type has been deduced
}

-예제 종료]

원본 작업

표준 라이브러리에 Y Combinator를 추가하기 위한 제안 A 제안을 보면 다음과 같은 작업 솔루션이 제공됩니다.

template<class Fun>
class y_combinator_result {
    Fun fun_;
public:
    template<class T>
    explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}

    template<class ...Args>
    decltype(auto) operator()(Args &&...args) {
        return fun_(std::ref(*this), std::forward<Args>(args)...);
    }
};

template<class Fun>
decltype(auto) y_combinator(Fun &&fun) {
    return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
}

그리고 그것은 당신의 예가 불가능하다고 명시 적으로 말합니다.

C ++ 11 / 14 람다는 재귀를 권장하지 않습니다. 람다 함수의 본문에서 람다 개체를 참조 할 방법이 없습니다.

그리고 그것은 Richard Smith가 clang이 당신에게주는 오류를 암시 하는 토론을 참조 합니다 .

나는 이것이 일류 언어 기능으로 더 좋을 것이라고 생각합니다. 코나 이전 회의 시간이 부족했지만 람다에게 이름을 부여 할 수 있도록 논문을 작성하려고했습니다.

auto x = []fib(int a) { return a > 1 ? fib(a - 1) + fib(a - 2) : a; };

여기서 'fib'는 람다의 * this와 동일합니다 (람다의 클로저 유형이 불완전 함에도 불구하고 이것이 작동하도록 허용하는 몇 가지 성가신 특수 규칙과 함께).

Barry 는 이것이 가능하지 않은 이유를 설명하고 제한을 우회하며 오늘날이를 달성하는 방법을 보여주는 후속 제안 Recursive lambdas 를 지적 dcl.spec.auto#9했습니다.

Lambda는 로컬 코드 리팩토링에 유용한 도구입니다. 그러나 때때로 우리는 직접 재귀를 허용하거나 클로저가 연속으로 등록되도록 허용하기 위해 자체 내에서 람다를 사용하려고합니다. 이것은 현재 C ++에서 잘 수행하기가 놀랍도록 어렵습니다.

예:

  void read(Socket sock, OutputBuffer buff) {
  sock.readsome([&] (Data data) {
  buff.append(data);
  sock.readsome(/*current lambda*/);
}).get();

}

자체에서 람다를 참조하려는 자연스러운 시도 중 하나는 변수에 저장하고 참조로 해당 변수를 캡처하는 것입니다.

 auto on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

그러나 이것은 의미 적 순환 성으로 인해 가능하지 않습니다 . 자동 변수의 유형은 lambda-expression이 처리 될 때까지 추론되지 않으므로 lambda-expression이 변수를 참조 할 수 없습니다.

또 다른 자연스러운 접근 방식은 std :: function을 사용하는 것입니다.

 std::function on_read = [&] (Data data) {
  buff.append(data);
  sock.readsome(on_read);
};

이 접근 방식은 컴파일되지만 일반적으로 추상화 패널티를 도입합니다. std :: function은 메모리 할당을 유발할 수 있으며 람다를 호출하려면 일반적으로 간접 호출이 필요합니다.

오버 헤드가없는 솔루션의 경우 로컬 클래스 유형을 명시 적으로 정의하는 것보다 더 좋은 방법은 없습니다.


@ Cheersandhth. - 알프 나는 표준 견적도 접근 작동 왜 삭제합니다 이후 관련되지 않도록 용지를 읽은 후 표준 견적을 찾는 결국
Shafik Yaghmour을

""추정되지 않은 자리 표시 자 유형을 가진 엔티티의 이름이 표현식에 나타나면 프로그램의 형식이 잘못된 self것 입니다. "그래도 프로그램에서이 항목이 표시 되지 않습니다 . 그런 엔티티로 보이지 않습니다.
n. '대명사'm.

@nm은 가능한 문구 외에도 예제가 문구와 일치하는 것처럼 보이며 예제가 문제를 명확하게 보여줍니다. 현재 도움이 더 필요하다고 생각하지 않습니다.
Shafik Yaghmour

13

clang이 옳은 것 같습니다. 간단한 예를 고려하십시오.

auto it = [](auto& self) {
    return [&self]() {
      return self(self);
    };
};
it(it);

컴파일러처럼 살펴 보겠습니다 (약간).

  • 의 유형 it입니다 Lambda1템플릿 호출 연산자와 함께.
  • it(it); 호출 연산자의 인스턴스화를 트리거합니다.
  • 템플릿 호출 연산자의 반환 유형은 auto이므로 추론해야합니다.
  • 유형의 첫 번째 매개 변수를 캡처하는 람다를 반환합니다 Lambda1.
  • 그 람다에는 호출 유형을 반환하는 호출 연산자도 있습니다. self(self)
  • 고시 : self(self)정확히 우리가 시작한 것입니다!

따라서 유형을 추론 할 수 없습니다.


의 반환 유형 Lambda1::operator()은 간단 Lambda2합니다. 그런 다음 해당 내부 람다 식 내에서의 반환 유형 인 self(self), Lambda1::operator()또한라고 알려져 있습니다 Lambda2. 아마도 공식적인 규칙은 그 사소한 추론을 방해하지만 여기에 제시된 논리는 그렇지 않습니다. 여기서 논리는 단언에 해당합니다. 공식적인 규칙이 방해가된다면 그것은 공식적인 규칙의 결함입니다.
건배와 hth. - 알프

@ Cheersandhth.-Alf 반환 유형이 Lambda2라는 데 동의하지만, 이것이 당신이 제안하는 것이기 때문입니다 : Lambda2의 호출 연산자 반환 유형의 추론을 지연시키기 때문에 당신은 추론되지 않은 호출 연산자를 가질 수 없다는 것을 알고 있습니다. 그러나 이것은 매우 기본적이기 때문에 이것에 대한 규칙을 변경할 수 없습니다.
Rakete1111

9

글쎄, 코드가 작동하지 않습니다. 그러나 이것은 :

template<class F>
struct ycombinator {
  F f;
  template<class...Args>
  auto operator()(Args&&...args){
    return f(f, std::forward<Args>(args)...);
  }
};
template<class F>
ycombinator(F) -> ycombinator<F>;

테스트 코드 :

ycombinator bob = {[x=0](auto&& self)mutable{
  std::cout << ++x << "\n";
  ycombinator ret = {self};
  return ret;
}};

bob()()(); // prints 1 2 3

귀하의 코드는 UB이고 형식이 잘못되어 진단이 필요하지 않습니다. 재미 있네요. 그러나 둘 다 독립적으로 고칠 수 있습니다.

첫째, UB :

auto it = [&](auto self) { // outer
  return [&](auto b) { // inner
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};
it(it)(4)(5)(6);

외부는 self값을 취하고 내부는 self참조로 캡처 한 다음 나중에 반환 하기 때문에 이것은 UB입니다.outer 실행이 완료된 입니다. 그래서 segfaulting은 확실히 괜찮습니다.

수정 사항 :

[&](auto self) {
  return [self,&a](auto b) {
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};

코드는 잘못된 형식입니다. 이를 확인하기 위해 람다를 확장 할 수 있습니다.

struct __outer_lambda__ {
  template<class T>
  auto operator()(T self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      T self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};
__outer_lambda__ it{a};
it(it);

이것은 인스턴스화합니다 __outer_lambda__::operator()<__outer_lambda__>.

  template<>
  auto __outer_lambda__::operator()(__outer_lambda__ self) const {
    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };
    return __inner_lambda__{a, self};
  }
  int& a;
};

따라서 다음으로 반환 유형을 결정해야합니다 __outer_lambda__::operator().

우리는 그것을 한 줄씩 살펴 봅니다. 먼저 __inner_lambda__유형 을 만듭니다 .

    struct __inner_lambda__ {
      template<class B>
      auto operator()(B b) const {
        std::cout << (a + b) << std::endl;
        return self(self);
      }
      int& a;
      __outer_lambda__ self;
    };

이제 거기를보세요 . 반환 유형은 self(self), 또는 __outer_lambda__(__outer_lambda__ const&)입니다. 그러나 우리는의 반환 유형을 추론하는 중 __outer_lambda__::operator()(__outer_lambda__)입니다.

당신은 그렇게 할 수 없습니다.

실제로의 반환 유형은의 반환 유형에 __outer_lambda__::operator()(__outer_lambda__)실제로 의존 __inner_lambda__::operator()(int)하지 않지만 C ++는 반환 유형을 추론 할 때 신경 쓰지 않습니다. 단순히 코드를 한 줄씩 확인합니다.

그리고 self(self)우리가 추론하기 전에 사용됩니다. 잘못된 프로그램입니다.

self(self)나중에 숨겨서 패치 할 수 있습니다 .

template<class A, class B>
struct second_type_helper { using result=B; };

template<class A, class B>
using second_type = typename second_type_helper<A,B>::result;

int main(int argc, char* argv[]) {

  int a = 5;

  auto it = [&](auto self) {
      return [self,&a](auto b) {
        std::cout << (a + b) << std::endl;
        return self(second_type<decltype(b), decltype(self)&>(self) );
      };
  };
  it(it)(4)(6)(42)(77)(999);
}

이제 코드가 정확하고 컴파일됩니다. 그러나 나는 이것이 약간의 해킹이라고 생각합니다. ycombinator를 사용하십시오.


아마도 (IDK)이 설명은 람다에 대한 공식적인 규칙에 맞습니다. 그러나 템플릿 재 작성 측면에서 내부 람다의 templated 반환 유형은 operator()일반적으로 인스턴스화 될 때까지 추론 될 수 없습니다 (일부 유형의 인수로 호출 됨). 따라서 수동 기계와 같은 템플릿 기반 코드 재 작성이 잘 작동합니다.
건배와 hth. - 알프

@cheers 코드가 다릅니다. inner는 코드의 템플릿 클래스이지만 내 또는 OP 코드에는 없습니다. 템플릿 클래스 메서드는 호출 될 때까지 인스턴스화가 지연되므로 중요합니다.
Yakk-Adam Nevraumont

템플릿 함수 내에 정의 된 클래스는 해당 함수 외부의 템플릿 클래스와 동일합니다. C ++ 규칙은 로컬 사용자 정의 클래스의 멤버 템플릿을 허용하지 않기 때문에 템플릿 멤버 함수가있는 경우 데모 코드에서 함수 외부에서 정의해야합니다. 이 공식적인 제한은 컴파일러가 스스로 생성하는 모든 것에 적용되지 않습니다.
건배와 hth. - 알프

7

컴파일러가 람다 식에 대해 생성하거나 생성해야하는 클래스 측면에서 코드를 다시 작성하는 것은 쉽습니다.

이 작업이 완료되면 주요 문제는 매달려있는 참조 일 뿐이며 코드를 받아들이지 않는 컴파일러는 람다 부서에서 다소 문제가 있음이 분명합니다.

재 작성은 순환 종속성이 없음을 보여줍니다.

#include <iostream>

struct Outer
{
    int& a;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner( a, self );    //! Original code has dangling ref here.
    }

    struct Inner
    {
        int& a;
        Outer& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Outer& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

원본 코드의 내부 람다가 템플릿 유형의 항목을 캡처하는 방식을 반영하는 완전한 템플릿 버전 :

#include <iostream>

struct Outer
{
    int& a;

    template< class > class Inner;

    // Actually a templated argument, but always called with `Outer`.
    template< class Arg >
    auto operator()( Arg& self ) const
        //-> Inner
    {
        return Inner<Arg>( a, self );    //! Original code has dangling ref here.
    }

    template< class Self >
    struct Inner
    {
        int& a;
        Self& self;

        // Actually a templated argument, but always called with `int`.
        template< class Arg >
        auto operator()( Arg b ) const
            //-> Inner
        {
            std::cout << (a + b) << std::endl;
            return self( self );
        }

        Inner( int& an_a, Self& a_self ): a( an_a ), self( a_self ) {}
    };

    Outer( int& ref ): a( ref ) {}
};

int main() {

  int a = 5;

  auto&& it = Outer( a );
  it(it)(4)(6)(42)(77)(999);
}

나는 내부 기계의 이러한 템플릿이며, 공식적인 규칙이 금지하도록 설계되었다고 생각합니다. 그들이 원래 구조를 금지한다면.


문제는 template< class > class Inner;템플릿 operator()이 ... 즉시 화 된다는 것입니다 . 음, 잘못된 단어입니다. 쓴? ... Outer::operator()<Outer>외부 연산자의 반환 유형이 추론되기 전에. 그리고 그 자체에 Inner<Outer>::operator()대한 부름이 Outer::operator()<Outer>있습니다. 그리고 그것은 허용되지 않습니다. 이제 대부분의 컴파일러는하지 않는 주의self(self)그들이의 리턴 타입 추론 기다려야하기 때문에 Outer::Inner<Outer>::operator()<int>때를 위해 int. 현명한 전달됩니다. 그러나 그것은 코드의 잘못된 형식을 놓친다.
Yakk-Adam Nevraumont

함수 템플릿 ,이 인스턴스화 될 때까지 함수 템플릿의 반환 유형을 추론하기 위해 기다려야 한다고 생각 합니다Innner<T>::operator()<U> . 결국 반환 유형은 U여기에 따라 달라질 수 있습니다. 그렇지 않지만 일반적으로.
건배와 hth. - 알프

확실한; 그러나 불완전한 반환 유형 추론에 의해 유형이 결정되는 표현식은 여전히 ​​불법입니다. 일부 컴파일러는 게으르고 나중에 모든 것이 작동하는 시점까지 확인하지 않습니다.
Yakk-Adam Nevraumont
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.