Могу ли я получить возвращаемый тип auto для работы с лямбдами с одинаковыми сигнатурами, но разными перехватами? - PullRequest
2 голосов
/ 15 октября 2019

Я пытаюсь использовать auto в качестве типа возврата для возвращаемой лямбда-функции. Вот минимальный пример, который демонстрирует проблему, с которой я столкнулся:

#include <iostream>
#include <memory>

auto
get_func()
{
    auto i = std::make_unique<int>(2);
    if (*i == 1) {
        return [i=std::move(i)]() {
            return *i;
        };
    }
    return [](){ return 2; };
}

int
main(int argc, char* argv[])
{
    auto func = get_func();
    std::cout << "val: " << func() << std::endl;
    return 0;
}

На моем Mac я получаю следующую ошибку компиляции:

$ g++ -g -Wall -Werror -std=c++17 test.cc -o test
test.cc:13:5: error: 'auto' in return type deduced as '(lambda at test.cc:13:12)' here but deduced as '(lambda at test.cc:9:16)' in earlier return statement
    return [](){ return 2; };
    ^
1 error generated.

Правильно, они обавыводится как lambda. И у них обоих одинаковая подпись void(void). Я вижу проблемы, потому что они указывают разные захваты? Если это так, каковы мои варианты получения вызова той же функции для вызывающей стороны (как видно из вызова cout в main).

Ответы [ 3 ]

5 голосов
/ 15 октября 2019

Я вижу проблемы, потому что они указывают разные захваты?

Вы бы увидели проблему, даже если бы они были в точности одинаковыми, токен для токена.

КаждыйЛямбда-выражение создает уникальный тип замыкания, который отличается от любого типа замыкания, созданного любым другим лямбда-выражением. Нет никакой разницы между этой разницей, поэтому auto удержание не может быть успешным.

Вам нужно будет ввести стереть фактический функтор, если вы намереваетесь вернуть две разные лямбда-выражения (либо с std::function, либо с пользовательским типом)поддерживает только семантику перемещения). Это, или, возможно, свернуть всю логику в одну лямбду:

auto
get_func()
{
    auto i = std::make_unique<int>(2);
    return [i=std::move(i)]() {
        if (*i == 1) {
            return *i;
        }
        return 2;
    };
}
2 голосов
/ 15 октября 2019

Я думаю, что лучший вариант (предвещает каламбур) - это решение StoryTeller для помещения логики в одну лямбду.

В качестве альтернативы вы можете использовать вариант длядержи свою лямбду. И вы можете создать простую обертку вокруг него:

template <class... Fs>
struct Lambda_fixed_variant
{
    std::variant<Fs...> f_;

    template <class L>
        // requires std::is_constructible_v<decltype(f_), std::in_place_type_t<L>, L&&>
    Lambda_fixed_variant(L l)
        : f_{std::in_place_type_t<L>{}, std::move(l)}
    {}

    template <class... Args>
    auto operator()(Args&&... args) const
    {
        return std::visit([&] (const auto& l) {
                return l(std::forward<Args>(args)...);
            },
            f_
            );
    }
};

auto get_func()
{
    auto i = std::make_unique<int>(2);

    auto l1 = [i=std::move(i)]() { return *i; };
    auto l2 = [](){ return 2; };

    using L1 = decltype(l1);
    using L2 = decltype(l2);

    if (true)
    {
        return Lambda_fixed_variant<L1, L2>{std::move(l1)};
    }
    return Lambda_fixed_variant<L1, L2>{l2};
}
2 голосов
/ 15 октября 2019

И они оба имеют одинаковую подпись void(void).

Хотя лямбды имеют одинаковую подпись operator(), они не относятся к одному и тому же типу класса. Это разные объекты с разными типами. Для автоматического вывода типа возврата требуется, чтобы все операторы возврата имели одинаковый тип, которого у вас нет.

В этом случае, когда у вас есть разные типы, вам нужен общий тип возврата. Вы можете получить это с помощью std::function, поскольку он позволяет вам возвращаться по типу operator() вместо лямбда-типа. Это дает вам

std::function<void(void)> get_func()
{
    auto i = std::make_unique<int>(2);
    if (*i == 1) {
        return [i=std::move(i)]() {
            return *i;
        };
    }
    return [](){ return 2; };
}
...