Cort Ammonの受け入れられた答えは良いですが、実装可能性についてもう1つ重要な点があると思います。
「one.cpp」と「two.cpp」の2つの異なる翻訳単位があるとします。
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);
の2つのオーバーロードは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」ではのマングルされた名前と同じであるextern void foo(A2)
私たちが一緒に2つのオブジェクト・ファイルをリンクすることができそうという、「one.cpp」インチ これは、 2つのタイプが「同じタイプ」であることの物理的な意味です。本質的には、別々にコンパイルされたオブジェクトファイル間のABI互換性に関するものです。
C ++コンパイラは、とが「同じタイプ」であることを確認する必要はありません。(実際、それらが異なるタイプであることを確認する必要がありますが、それは今のところそれほど重要ではありません。)B1
B2
どのような物理的メカニズムのことを確保するためのコンパイラを使用しないA1
と、A2
「同じタイプ」ですか?
typedefを掘り下げてから、型の完全修飾名を調べます。これは、という名前のクラスタイプA
です。(まあ、::A
それはグローバル名前空間にあるので。)したがって、どちらの場合も同じタイプです。それは理解しやすいです。さらに重要なのは、実装が簡単なことです。2つのクラスタイプが同じタイプであるかどうかを確認するには、それらの名前を取得してstrcmp
。を実行します。クラス型を関数のマングル名にマングルするには、名前に文字数を記述し、その後にそれらの文字を記述します。
そのため、名前付きタイプは簡単にマングルできます。
どのような物理的なメカニズムかもしれないことを確実にするために、コンパイラの使用B1
およびB2
C ++は、同じタイプであることを、それらを必要と架空の世界では「同じタイプ」ですか?
タイプがいないのでまあ、それは、型の名前を使用することができませんでし持って名前を。
多分それはラムダの本体のテキストをどういうわけかエンコードすることができます。しかし、実際にb
は「one.cpp」のinは「two.cpp」のinと微妙に異なるため、これは少し厄介ですb
。「one.cpp」にはがx+1
あり、「two.cpp」にはがありx + 1
ます。私たちは、この空白の差があることのどちらかと言うルールを思い付くする必要があると思いますので、ない問題、またはそれがあることない(それらすべての後に、異なる種類にする)、またはことを多分ない(多分プログラムの有効性は、実装定義であります、または多分それは「不正な形式の診断は必要ありません」)。とにかく、A
この問題を解決する最も簡単な方法は、各ラムダ式が一意の型の値を生成すると言うことです。その場合、異なる変換単位で定義された2つのラムダタイプは間違いなく同じタイプではありません。単一の翻訳ユニット内で、ソースコードの先頭から数えるだけでラムダタイプに「名前を付ける」ことができます。
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
、このTUstruct 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
はデフォルトで初期化された関数ポインタになり、おそらくセグメンテーション違反になります。それは、役に立たないようなものです。