【问题标题】:Can I get return type auto to work with lambdas of the same signature but different captures?我可以让返回类型 auto 与具有相同签名但不同捕获的 lambda 一起使用吗?
【发布时间】:2020-02-12 07:47:37
【问题描述】:

我正在尝试使用 auto 作为返回的 lambda 函数的返回类型。这是一个演示我遇到的问题的最小示例:

#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) 签名。我看到问题是因为它们指定了不同的捕获吗?如果是这样,我有哪些选择可以让调用者使用相同的函数调用(如 main 中的 cout 调用所示)。

【问题讨论】:

标签: c++ lambda c++17 auto


【解决方案1】:

我看到问题是因为它们指定了不同的捕获吗?

即使它们完全一样,你也会看到一个问题,一个令牌一个令牌。

每个 lambda 表达式创建一个 唯一 闭包类型,它不同于任何其他 lambda 创建的任何闭包类型。没有调和这个差异,所以auto推演不能成功。

如果您打算返回两个不同的 lambda(使用 std::function 或支持仅移动语义的自定义类型),则需要键入擦除实际函子。那,或者可能将整个逻辑整合到一个 lambda 中:

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

【讨论】:

  • 你能发布一个这个工作的代码示例吗?正如我向@NathanOliver 提到的,捕获的 unique_ptr 似乎排除了 std::function。
  • @firebush - 这总是让我着迷。恐怕std::function 确实不行。这是标准库的僵局,需要自定义类型擦除函子。
  • 谢谢@storyteller。这证实了这一限制。
【解决方案2】:

而且它们都有相同的void(void) 签名。

虽然 lambda 具有相同的 operator() 签名,但它们不是相同的类类型。它们是具有不同类型的不同对象。自动返回类型推导要求所有返回语句具有相同的类型,而您在这里没有。

在这种情况下,你有不同的类型,你需要一个共同的返回类型。您可以使用 std::function 来获得它,因为它允许您通过 operator() 类型而不是 lambda 类型返回。这给了你

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; };
}

【讨论】:

  • 我想我在第 22 个问题中。您的示例无法编译:我不能使用 std::function 因为我需要使用移动捕获的 std::make_unique 。由于 std::function 需要复制能力(std::unique_ptr 不支持),因此无法编译。
  • @firebush D'oh。错过了它只是移动。是的,这行不通。您需要一个支持仅移动函子的版本,例如:stackoverflow.com/questions/25330716/…
  • 好的,这很有帮助,因为它告诉我我必须使用的参数。我将使用 shared_ptr 代替,因为它是可复制的,并通过捕获将其移动到 lambda 中。然后我会按照你的建议使用 std::function 。谢谢。
  • @firebush 没问题。很高兴能提供帮助。
【解决方案3】:

我认为最好的变体(预示双关语)是 StoryTeller 将逻辑放入单个 lambda 的解决方案。

为了好玩,作为替代方案,您可以使用变体来保存您的 lambda。您可以围绕它创建一个简单的包装器:

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};
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-28
    • 2020-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多