Cort Ammon'un kabul ettiği cevap iyi, ancak uygulanabilirlikle ilgili yapılması gereken bir önemli nokta daha var.
İki farklı çeviri birimim olduğunu varsayalım, "one.cpp" ve "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);
İki aşırı yükleme foo
aynı tanımlayıcıyı ( foo
) kullanır ancak farklı karışık adlara sahiptir. (POSIX-ish sistemlerde kullanılan Itanium ABI'de, karıştırılmış isimler _Z3foo1A
ve bu özel durumda _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 ++ derleyicisi gerekir ait parçalanmış adı sağlamak void foo(A1)
"two.cpp" içinde mangled adıyla aynıdır extern void foo(A2)
Birlikte iki nesne dosyalarını bağlayabilirsiniz böylece, "one.cpp" in. Bu, "aynı tür" olan iki türün fiziksel anlamıdır : temelde ayrı olarak derlenen nesne dosyaları arasındaki ABI uyumluluğu ile ilgilidir.
C ++ derleyicisi olduğu değil emin olmak için gerekli B1
ve B2
vardır "Aynı tip." (Aslında, farklı tipte olmalarını sağlamak gerekiyor; ancak bu şu anda o kadar önemli değil.)
Derleyici bunu sağlamak için hangi fiziksel mekanizmayı kullanıyor A1
ve A2
"aynı tip" mi?
Basitçe yazım biçimlerini inceler ve ardından türün tam olarak nitelenmiş adına bakar. Adlı bir sınıf türüdür A
. ( ::A
Küresel isim alanında olduğu için.) Yani her iki durumda da aynı tip. Anlaması kolay. Daha da önemlisi, uygulanması kolaydır . İki sınıf türünün aynı tür olup olmadığını görmek için adlarını alır ve bir strcmp
. Bir sınıf türünü bir işlevin karıştırılmış ismine karıştırmak için, onun adına karakter sayısını ve ardından bu karakterleri yazarsınız.
Yani, adlandırılmış türlerin karıştırılması kolaydır.
Ne fiziksel mekanizma olabileceğini derleyici kullanımı sağlamak için B1
ve B2
C ++ aynı tip olmalarını gerektiğinde "aynı tip," varsayımsal dünyada nelerdir?
Tipi değil çünkü Eh, türünün adını kullanamadı sahip bir isim.
Belki bir şekilde lambda gövdesinin metnini kodlayabilirdi . Ama bu biraz garip olurdu, çünkü aslında b
"one.cpp" içindeki "two.cpp" deki biraz farklı b
: "one.cpp" x+1
ve "two.cpp" var x + 1
. Bu boşluk fark ya şeklindeki bir kural ile gelip olurdu Yani yok olsun ya da o yapar (sonuçta onları farklı yapmak) veya bu belki öyle (belki programın geçerlilik uygulaması-tanımlanır veya belki de "teşhis gerektirmeyen kötü biçimlidir"). Her neyse,A
Zorluktan çıkmanın en kolay yolu, her lambda ifadesinin benzersiz tipte değerler ürettiğini söylemektir. O zaman farklı çeviri birimlerinde tanımlanan iki lambda türü kesinlikle aynı tür değildir . Tek bir çeviri biriminde, lambda türlerini yalnızca kaynak kodun başından itibaren sayarak "adlandırabiliriz":
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; };
}
Elbette bu isimlerin sadece bu çeviri birimi içinde anlamı vardır. Bu OG'ler $_0
her zaman diğer bazı TU'larla aynı tipte $_0
olsa da, bu OG'ler struct A
her zaman diğer bazı OG'lerden farklı bir tiptir struct A
.
Bu arada, "lambda metnini kodla" fikrimizin başka bir ince problemi olduğuna dikkat edin: lambdas $_2
ve $_3
tamamen aynı metinden oluşuyor , ancak açıkça aynı tür olarak kabul edilmemeleri gerekiyor !
Bu arada, C ++, derleyicinin rasgele bir C ++ ifadesinin metnini nasıl karıştıracağını bilmesini gerektirir .
template<class T> void foo(decltype(T())) {}
template void foo<int>(int);
Ancak C ++, derleyicinin rastgele bir C ++ ifadesini nasıl karıştıracağını bilmesini gerektirmez (henüz) . decltype([](){ ...arbitrary statements... })
C ++ 20'de bile hala biçimsiz.
Ayrıca / kullanarak adsız bir türe yerel bir takma ad vermenin kolay olduğuna dikkat edin . Sorunuzun bu şekilde çözülebilecek bir şey yapmaya çalışmaktan kaynaklanmış olabileceğine dair bir his var.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);
}
EKLENMEK İÇİN DÜZENLENMİŞTİR: Diğer cevaplarla ilgili yorumlarınızın bazılarını okurken, nedenini merak ediyormuşsunuz gibi geliyor
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)>);
Bunun nedeni, yakalamasız lambdaların varsayılan olarak oluşturulabilir olmasıdır. (C ++ 'da yalnızca C ++ 20'den itibaren, ancak kavramsal olarak her zaman doğru olmuştur.)
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);
Eğer denediyseniz default_construct_and_call<decltype(&add1)>
, t
varsayılan olarak başlatılmış bir işlev işaretçisi olur ve muhtemelen segfault olursunuz. Faydalı değil gibi.