ラムダ関数をオーバーロードする


14

単純なローカルラムダ関数をオーバーロードする方法は?

元の問題の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です。


7
ラムダは関数ではなく、オブジェクトなので、オーバーロードが適用されることはありません。translate同じ名前を再利用できないローカル変数です。
user7860670

回答:


10

いいえ、ラムダをオーバーロードすることはできません!

ラムダは匿名ファンクタ(つまり、名前のない関数オブジェクト)であり、単純な関数ではありません。したがって、これらのオブジェクトをオーバーロードすることはできません。あなたが基本的にしようとしていることはほとんどです

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 ++では同じ変数名を再利用できないため、これは不可能です。


しかし、中に我々はif constexpr1がコンパイル時にtrueの場合にのみブランチをインスタンス化することができましたで。

可能な解決策は次のとおりです。

  • 単一の可変長テンプレートラムダ。または
  • ジェネリックラムダを使用decltype して、if constexprチェックに使用するパラメーターのタイプを見つけます(credits @NathanOliver

variabeテンプレートを使用すると、次のようなことができます。オンラインでライブデモをご覧ください

#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]);

一般的なラムダを使用すると(以降)、上記は次のようになりますオンラインでライブデモを参照してください

#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]));

3
私はこれがすごい
スヌーピー

1
まず、あなたelse ifがする必要がありますelse if constexpr。次に、なぜ変数テンプレートを使用するのですか?あなただけの一般的なラムダを作ることができ、あなたcheclsはなるだろうif constexpr (std::is_same_v<decltype(idx), int>)else if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver

6

ラムダは基本的に、ローカルに定義されたファンクタの構文糖です。私が知る限り、これらは、さまざまなパラメーターで呼び出されるようにオーバーロードされることを意図されていません。すべてのラムダ式は異なる型であるため、即時のエラーは別として、コードが意図したとおりに機能しないことに注意してください。

ただし、オーバーロードされたでファンクタを定義できます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;
}

ちょっと待って、ラムダ構文を呼んでいますか?
user7860670

1
@VTT構文が簡潔であることは素晴らしいことです。いくつかのより古いものと比較してそれは悪くない
idclev 463035818

5

したがって、名前をオーバーロードするためのルールは、関数名の特定の種類の検索(無料とメソッドの両方)にのみ適用されます

ラムダは関数ではなく、関数呼び出し演算子を持つオブジェクトです。したがって、2つの異なるラムダ間でオーバーロードが発生することはありません。

これで、オーバーロード解決を取得して関数オブジェクトを操作できますが、単一オブジェクトのスコープ内でのみ可能です。そして、複数ある場合operator()、過負荷解決はそれらの間で選択できます。

ただし、ラムダには、複数のを持つ明確な方法はありませんoperator()。簡単な()ユーティリティクラスを作成して、次のことを支援できます。

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 ++機能の方法でサポートするものと一致するソリューションを見つけることは難しくありません。


私が理解しているように、各「オーバーロードされた」ラムダには独自のキャプチャブロックがあります。つまり、それらのラムダは何も共有しません(そして、同じデータを何度もキャプチャするCPU時間を浪費します)。たぶんC ++標準はそれを修正する何かを持っていますか?または、オプションのみvariadic generic lamda+でif constexpr通話を分離しますか?
CM

@CMスタックオーバーフローについて質問するには、[コメントの追加]ボタンではなく、右上にある[質問する]ボタンを押してください。ありがとう!
Yakk-Adam Nevraumont
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.