Jawaban yang diterima Cort Ammon bagus, tetapi saya pikir ada satu hal penting lagi yang harus dibuat tentang implementabilitas.
Misalkan saya memiliki dua unit terjemahan yang berbeda, "one.cpp" dan "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);
Dua kelebihan foo
penggunaan menggunakan identifier ( foo
) yang sama tetapi memiliki nama yang rusak berbeda. (Dalam Itanium ABI yang digunakan pada sistem POSIX-ish, nama yang rusak adalah _Z3foo1A
dan, dalam kasus khusus ini _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) {}
Kompilator C ++ harus memastikan bahwa nama rusak void foo(A1)
di "two.cpp" sama dengan nama rusak extern void foo(A2)
di "one.cpp", sehingga kita dapat menautkan dua file objek bersama-sama. Ini adalah arti fisik dari dua jenis yang menjadi "tipe yang sama": ini pada dasarnya tentang kompatibilitas ABI antara file objek yang dikompilasi secara terpisah.
Compiler C ++ tidak diperlukan untuk memastikan B1
dan B2
merupakan "tipe yang sama". (Sebenarnya, diperlukan untuk memastikan bahwa mereka berbeda tipe; tapi itu tidak sepenting sekarang.)
Mekanisme fisik apa yang digunakan kompilator untuk memastikan bahwa A1
dan A2
merupakan "tipe yang sama"?
Itu hanya menggali melalui typedefs, dan kemudian melihat nama tipe yang sepenuhnya memenuhi syarat. Itu adalah tipe kelas bernama A
. (Yah, ::A
karena ini ada di namespace global.) Jadi itu tipe yang sama di kedua kasus. Itu mudah dimengerti. Lebih penting lagi, ini mudah diterapkan . Untuk melihat apakah dua tipe kelas adalah tipe yang sama, Anda mengambil namanya dan melakukan a strcmp
. Untuk mengacaukan tipe kelas menjadi nama fungsi yang rusak, Anda menulis jumlah karakter dalam namanya, diikuti dengan karakter tersebut.
Jadi, tipe bernama mudah untuk diatur.
Mekanisme fisik apa yang mungkin digunakan compiler untuk memastikan bahwa B1
dan B2
merupakan "tipe yang sama", dalam dunia hipotetis di mana C ++ mengharuskan mereka untuk menjadi tipe yang sama?
Yah, itu tidak bisa menggunakan nama tipe, karena tipe tidak memiliki nama.
Mungkin entah bagaimana itu bisa menyandikan teks tubuh lambda. Tapi itu akan agak canggung, karena sebenarnya b
di "one.cpp" sedikit berbeda dari b
di "two.cpp": "one.cpp" has x+1
dan "two.cpp" has x + 1
. Jadi kita harus membuat aturan yang mengatakan bahwa perbedaan whitespace ini tidak penting, atau memang begitu (bagaimanapun juga membuat mereka berbeda tipe), atau mungkin memang begitu (mungkin validitas program ditentukan oleh implementasi , atau mungkin itu "cacat tidak diperlukan diagnostik"). Bagaimanapun,A
Jalan keluar termudah dari kesulitan ini adalah dengan mengatakan bahwa setiap ekspresi lambda menghasilkan nilai dari tipe yang unik. Maka dua jenis lambda yang ditentukan dalam unit terjemahan yang berbeda pasti bukan jenis yang sama . Dalam satu unit terjemahan, kita dapat "memberi nama" jenis lambda hanya dengan menghitung dari awal kode sumber:
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; };
}
Tentu saja nama-nama ini hanya memiliki arti dalam unit terjemahan ini. TU $_0
ini selalu berbeda jenisnya dengan TU lain $_0
, meskipun TU struct A
ini selalu sama jenisnya dengan TU lain struct A
.
Ngomong-ngomong, perhatikan bahwa gagasan "menyandikan teks lambda" kami memiliki masalah halus lainnya: lambda $_2
dan $_3
terdiri dari teks yang persis sama , tetapi gagasan itu jelas tidak boleh dianggap jenis yang sama !
Ngomong-ngomong, C ++ memang membutuhkan compiler untuk mengetahui cara mengacaukan teks dari ekspresi C ++ arbitrer , seperti pada
template<class T> void foo(decltype(T())) {}
template void foo<int>(int);
Tapi C ++ tidak (belum) membutuhkan compiler tahu bagaimana mangle sebuah C ++ sewenang-wenang pernyataan . decltype([](){ ...arbitrary statements... })
bentuknya masih buruk bahkan dalam C ++ 20.
Juga perhatikan bahwa mudah memberikan alias lokal ke tipe tanpa nama menggunakan typedef
/ using
. Saya merasa pertanyaan Anda mungkin muncul dari mencoba melakukan sesuatu yang dapat diselesaikan seperti ini.
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);
}
DIEDIT UNTUK DITAMBAHKAN: Dari membaca beberapa komentar Anda di jawaban lain, sepertinya Anda bertanya-tanya mengapa
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)>);
Itu karena lambda yang tidak dapat ditangkap dapat dibangun secara default. (Dalam C ++ hanya pada C ++ 20, tetapi itu selalu benar secara konseptual .)
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);
Jika Anda mencoba default_construct_and_call<decltype(&add1)>
, t
akan menjadi penunjuk fungsi yang diinisialisasi default dan Anda mungkin akan segfault. Itu, sepertinya, tidak berguna.