Cort Ammon의 대답 은 훌륭하지만 구현 가능성에 대해 한 가지 더 중요한 점이 있다고 생각합니다.
"one.cpp"와 "two.cpp"라는 두 개의 다른 번역 단위가 있다고 가정합니다.
struct A { int operator()(int x) const { return x+1; } };
auto b = [](int x) { return x+1; };
using A1 = A;
using B1 = decltype(b);
extern void foo(A1);
extern void foo(B1);
의 두 오버로드 foo
는 동일한 식별자 ( foo
)를 사용하지만 다른 이름을 엉망으로 만듭니다. (POSIX 시스템에서 사용되는 Itanium ABI에서 잘린 이름은이며이 _Z3foo1A
경우에는 _Z3fooN1bMUliE_E
.)
struct A { int operator()(int x) const { return x + 1; } };
auto b = [](int x) { return x + 1; };
using A2 = A;
using B2 = decltype(b);
void foo(A2) {}
void foo(B2) {}
C ++ 컴파일러 는void foo(A1)
"two.cpp"의 변경된 이름이 "one.cpp"의 변경된 이름과 동일한 지 확인 extern void foo(A2)
해야 두 개체 파일을 함께 연결할 수 있습니다. 이것은 "동일한 유형"인 두 유형 의 물리적 의미 입니다. 본질적으로 별도로 컴파일 된 개체 파일 간의 ABI 호환성에 관한 것입니다.
C ++ 컴파일러는 및 이 "동일한 유형" 인지 확인하는 데 필요 하지 않습니다 . (사실 서로 다른 유형인지 확인해야하지만 지금은 그렇게 중요하지 않습니다.)B1
B2
어떤 물리적 메커니즘을하는 것을 보장하기 위해 컴파일러 사용을 수행 A1
하고A2
"동일 유형"인가?
단순히 typedef를 통해 잠복 한 다음 형식의 정규화 된 이름을 확인합니다. 라는 클래스 유형 A
입니다. ( ::A
글로벌 네임 스페이스에 있기 때문입니다.) 따라서 두 경우 모두 동일한 유형입니다. 이해하기 쉽습니다. 더욱 중요한 것은 쉽다 구현 . 두 클래스 유형이 동일한 유형인지 확인하려면 이름을 가져 와서strcmp
. 클래스 유형을 함수의 이름을 변경하려면 이름에 문자 수를 쓰고 그 뒤에 해당 문자를 입력합니다.
따라서 명명 된 유형은 쉽게 조작 할 수 있습니다.
어떤 물리적 인 메커니즘을 수있는 컴파일러 사용을 보장하기 위하여 B1
및 B2
C ++가 동일한 유형으로 그들을 필요한 경우 "동일 유형은"가상의 세계에?
글쎄, 유형의 이름을 사용할 수 없습니다. 유형 에는 이름을.
람다 본문의 텍스트 를 어떻게 든 인코딩 할 수 있습니다. 그러나 그것은 다소 어색 할 것입니다. 왜냐하면 실제로 b
"one.cpp"는 "two.cpp"에서와 미묘하게 다르기 때문입니다 b
: "one.cpp"는 가지고 x+1
있고 "two.cpp"는 x + 1
. 우리는이 공백 차이가 있음 중 하나라는 규칙을 마련 할 것 그래서 하지 않는 문제, 또는 그 수행 (결국 그들에게 다른 유형을), 또는 어쩌면 않습니다 (아마 프로그램의 유효성이 구현 정의 , 또는 "진단이 필요하지 않은 형식이 잘못되었습니다"). 어쨌든,A
가장 쉬운 방법은 각 람다식이 고유 한 유형의 값을 생성한다고 말하는 것입니다. 그러면 서로 다른 번역 단위로 정의 된 두 개의 람다 유형은 확실히 동일한 유형 이 아닙니다 . 단일 번역 단위 내에서 소스 코드의 시작 부분부터 계산하여 람다 유형을 "이름"할 수 있습니다.
auto a = [](){};
auto b = [](){};
auto f(int x) {
return [x](int y) { return x+y; };
}
auto g(float x) {
return [x](int y) { return x+y; };
}
물론 이러한 이름은이 번역 단위 내에서만 의미가 있습니다. 이 TU $_0
는 항상 다른 TU와 다른 유형 $_0
이지만,이 TU struct A
는 항상 다른 TU와 동일한 유형 struct A
입니다.
그건 그렇고, 우리의 "람다 텍스트 인코딩"아이디어에는 또 다른 미묘한 문제가 있습니다. 람다 $_2
이고 $_3
정확히 동일한 텍스트 로 구성 되지만 분명히 동일한 유형 으로 간주되어서는 안됩니다 !
그건 그렇고, C ++는 컴파일러가 임의의 C ++ 표현식 의 텍스트를 조작하는 방법을 알아야합니다 .
template<class T> void foo(decltype(T())) {}
template void foo<int>(int);
그러나 C ++는 컴파일러가 임의의 C ++ 문 을 조작하는 방법을 알 필요가 없습니다 . decltype([](){ ...arbitrary statements... })
C ++ 20에서도 여전히 잘못된 형식입니다.
또한 /를 사용하여 이름이 지정되지 않은 유형에 로컬 별칭을 제공 하는 것이 쉽습니다 . 당신의 질문이 이렇게 해결 될 수있는 일을하려고해서 나온 것 같다고 생각합니다.typedef
using
auto f(int x) {
return [x](int y) { return x+y; };
}
using AdderLambda = decltype(f(0));
int of_one(AdderLambda g) { return g(1); }
int main() {
auto f1 = f(1);
assert(of_one(f1) == 2);
auto f42 = f(42);
assert(of_one(f42) == 43);
}
추가 편집 : 다른 답변에 대한 귀하의 의견 중 일부를 읽어 보니 이유가 궁금하신 것 같습니다.
int add1(int x) { return x + 1; }
int add2(int x) { return x + 2; }
static_assert(std::is_same_v<decltype(add1), decltype(add2)>);
auto add3 = [](int x) { return x + 3; };
auto add4 = [](int x) { return x + 4; };
static_assert(not std::is_same_v<decltype(add3), decltype(add4)>);
캡처없는 람다는 기본 구성이 가능하기 때문입니다. (C ++에서는 C ++ 20에서만 가능하지만 항상 개념적으로 사실이었습니다.)
template<class T>
int default_construct_and_call(int x) {
T t;
return t(x);
}
assert(default_construct_and_call<decltype(add3)>(42) == 45);
assert(default_construct_and_call<decltype(add4)>(42) == 46);
당신이 시도하는 경우 default_construct_and_call<decltype(&add1)>
, t
기본 초기화 함수 포인터가 될 것이며, 당신은 아마 세그 폴트 것입니다. 그것은 유용하지 않은 것 같습니다.