确定具有不同签名的 lambda 的结果类型

determine result type of lambdas with different signature

提问人:francesco 提问时间:11/7/2023 更新时间:11/7/2023 访问量:88

问:

假设我有一个例程,它接收一个可调用的 Lambda 作为参数,并且允许这个 Lambda 有 2 个签名:它可以称为提供 int 或 2 ints。

问题是:如何确定此 Lambda 的结果类型。

下面的代码说明了该问题

#include <iostream>
#include <type_traits>

template <class Lambda>
auto f(Lambda&& f)
{
    using result_type = std::conditional_t<std::is_invocable_v<Lambda, int>, std::invoke_result_t<Lambda, int>, std::invoke_result_t<Lambda, int, int>>;
    result_type res;
    if constexpr (std::is_invocable_v<Lambda, int>) {
        res = f(3);
    }
    else {
        res =  f(3, 3);
    }
    
    return res;
}

int main()
{
    std::cout << f([](int x) -> int { return x; }) << std::endl;
    std::cout << f([](int x, int y) -> int { return x * y; }) << std::endl;

    return 0;
}

在 Coliru 上实时检查。

代码不编译,因为在里面,必须定义第二个和第三个模板参数,而这里显然只有一个。std::conditional_t

我可以考虑编写一个用于解决这个问题的特征,比如利用模板专业化。类似的东西

template <class F, bool T>
struct helper;

template <class F>
struct helper<F, true> {
    using type = std::invoke_result_t<F, int>;
};

template <class F>
struct helper<F, false> {
    using type = std::invoke_result_t<F, int, int>;
};

但在重新发明轮子之前:有没有使用 std 库的更简单的紧凑解决方案?我对 c++17 解决方案感兴趣。

C++ 模板 类型特征

评论

0赞 NathanOliver 11/7/2023
为什么要尝试将所有这些功能放在一个函数中?您有两个行为不同的重载,因此只需编写函数的两个重载:coliru.stacked-crooked.com/a/a5965368bb9ed845
0赞 NathanOliver 11/7/2023
此外,这是一个“已知问题”:stackoverflow.com/questions/28432977/...std::conditional
0赞 francesco 11/7/2023
@NathanOliver 感谢您的评论。显然,上面的例子要简单得多。在实际代码中,Lambda 的参数在 f() 中计算,然后进一步处理结果。在调用 Lambda 时插入一个,同时将其余代码保留在 f 中,就代码的可读性而言,似乎是一个简单的选择......if constexpr
0赞 463035818_is_not_an_ai 11/7/2023
抱歉,您的代码示例没有说明问题,因为在您的代码示例中,解决方案是微不足道的。你不需要拼出 godbolt.org/z/nde4PzMGhresult_type
1赞 NathanOliver 11/7/2023
这里还有另一种获取返回类型的方法:stackoverflow.com/questions/53673442/...

答:

3赞 Ted Lyngmo 11/7/2023 #1

我不确定这是否比你自己的类型特征有任何好处。它只是不同,它不允许歧义(当两个重载同样合适时),这可能是不需要的:

template<class L> auto foo(L&&) -> std::invoke_result_t<L, int>;
template<class L> auto foo(L&&) -> std::invoke_result_t<L, int, int>;

template <class Lambda>
auto f(Lambda&& f) {
    using result_type = decltype(foo(std::forward<Lambda>(f)));
    // ...
}

std::invoke_result::type- 如果使用参数调用,则 Callable 类型的返回类型。仅当可以在未计算的上下文中使用参数调用时才定义。FArgTypes...FArgTypes...

演示


或者使用你已经拥有的东西并直接初始化:res

template <class Lambda>
auto f(Lambda&& f) {
    decltype(auto) res = [&] {
        if constexpr (std::is_invocable_v<Lambda, int>) {
            return f(3);
        } else {
            return f(3, 3);
        }
    }();

    // do other stuff ...

    return res;
}

演示

1赞 463035818_is_not_an_ai 11/7/2023 #2

在您的示例中,您不需要 ,但这将执行以下操作:result_type

template <class Lambda>
auto f(Lambda&& f)
{
    if constexpr (std::is_invocable_v<Lambda, int>) {
        return f(3);
    }
    else {
        return f(3, 3);
    }
}

如果你在实际代码中确实需要,可以通过 或 从上面删除它。result_typefdecltype(f(the_lambda))std::invoke_result_t<f,Lambda>

2赞 Jarod42 11/7/2023 #3

从你的代码中,你只需要“延迟”评估:

using result_type =
    std::conditional_t<std::is_invocable_v<Lambda, int>,
       std::invoke_result<Lambda, int>,
       std::invoke_result<Lambda, int, int>>::type;

演示