単純なローカルラムダ関数をオーバーロードする方法は?
元の問題のSSE:
#include <iostream>
#include <map>
void read()
{
static std::string line;
std::getline(std::cin, line);
auto translate = [](int idx)
{
constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
return table[idx];
};
auto translate = [](char c)
{
std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
{'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[c];
};
int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
std::cout << r << c << std::endl;
}
int main()
{
read();
return 0;
}
エラーメッセージ
error: conflicting declaration 'auto translate'
note: previous declaration as 'read()::<lambda(int)> translate'
ユーザー入力を確認しないでください。これはSSEです。
いいえ、ラムダをオーバーロードすることはできません!
ラムダは 匿名ファンクタ (つまり、名前のない関数オブジェクト)であり、単純な関数ではありません。したがって、これらのオブジェクトをオーバーロードすることはできません。あなたが基本的にしようとしていることはほとんどです
struct <some_name>
{
int operator()(int idx) const
{
return {}; // some int
}
}translate; // >>> variable name
struct <some_name>
{
int operator()(char idx) const
{
return {}; // some int
}
}translate; // >>> variable name
C++では同じ変数名を再利用できないため、これは不可能です。
ただし、 c ++ 17 には if constexpr
があります。これにより、コンパイル時にtrueになる唯一のブランチをインスタンス化できます。
可能な解決策は次のとおりです。
if constexpr
チェックに decltype
を使用してパラメーターのタイプを見つけます。(credits@ NathanOliver)variabe template を使用すると、次のようなことができます。 ( ライブデモをオンラインで見る )
#include <type_traits> // std::is_same_v
template<typename T>
constexpr auto translate = [](T idx)
{
if constexpr (std::is_same_v<T, int>)
{
constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
return table[idx];
}
else if constexpr (std::is_same_v<T, char>)
{
std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[idx];
}
};
そしてそれを
int r = translate<int>(line[0]);
int c = translate<char>(line[1]);
一般的なラムダを使用して(- c ++ 14 以降)、上記は次のようになります:( ライブデモをオンラインで表示 )
#include <type_traits> // std::is_same_v
constexpr auto translate = [](auto idx)
{
if constexpr (std::is_same_v<decltype(idx), int>)
{
constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
return table[idx];
}
else if constexpr (std::is_same_v<decltype(idx), char>)
{
std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[idx];
}
};
今のようにラムダを呼び出します:
int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
ラムダは基本的に、ローカルで定義されたファンクタの構文糖です。私の知る限り、これらは、異なるパラメーターで呼び出されるようにオーバーロードすることを意図したものではありません。すべてのラムダ式は異なる型であるため、即時のエラーは別として、コードは意図したとおりに機能しません。
ただし、オーバーロードされたoperator()
を使用してファンクタを定義できます。これは、可能であれば、ラムダから得られるものとまったく同じになります。簡潔な構文を取得しないだけです。
何かのようなもの:
void read()
{
static std::string line;
struct translator {
int operator()(int idx) { /* ... */ }
int operator()(char x) { /* ... */ }
};
translator translate;
std::getline(std::cin, line);
int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
std::cout << r << c << std::endl;
}
したがって、名前のオーバーロードのルールは、関数名の特定の種類の検索(フリーとメソッドの両方)にのみ適用されます。
ラムダは関数ではなく、関数呼び出し演算子を持つオブジェクトです。したがって、2つの異なるラムダ間でオーバーロードが発生することはありません。
これで、オーバーロード解決を取得して関数オブジェクトを操作できますが、単一オブジェクトのスコープ内でのみ可能です。そして、複数のoperator()
がある場合、オーバーロード解決はそれらの間で選択できます。
ただし、ラムダには、複数のoperator()
を使用する明確な方法がありません。簡単な( c ++ 17 の)ユーティリティクラスを記述して、次のことを支援できます。
template<class...Fs>
struct overloaded : Fs... {
using Fs::operator()...;
};
控除ガイド:
template<class...Fs>
overloaded(Fs...) -> overloaded<Fs...>;
これら2つを使用すると、2つのラムダをオーバーロードできます。
static std::string line;
std::getline(std::cin, line);
auto translate_int = [](int idx){
constexpr static int table[8] {7,6,5,4,3,2,1,0};
return table[idx];
};
auto translate_char = [](char c) {
std::map<char, int> table { {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
{'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
return table[c];
};
auto translate = overloaded{ translate_int, translate_char };
int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
そして完了。
overloaded
の記述は c ++ 14 と c ++ 11 の両方で可能ですが、より多くの作業を必要とし、あまりエレガントではありません。問題を認識したら、特定のコンパイラがC++機能の方法でサポートするものと一致するソリューションを見つけることは難しくありません。