【问题标题】:Is there a way to detect at compile time whether a generic lambda can be successfully invoked with a given set of parameter types?有没有办法在编译时检测是否可以使用给定的一组参数类型成功调用泛型 lambda?
【发布时间】:2017-05-09 17:26:21
【问题描述】:

我希望能够在编译时确定,给定一个通用 lambda 类型,是否可以使用给定的一组参数类型来调用它。我有以下示例 C++14 实现:

#include <iostream>

// helper function; this overload handles the case that the call is possible
// use SFINAE with the extra template parameter to remove this from consideration when the
// call is ill-formed
template <typename Func, typename... Args, typename = decltype(std::declval<Func>()(std::declval<Args>()...))>
auto eval(Func f, Args &&... args) { return f(args...); }

// special type returned from `eval()` when the call can't be done
struct invalid_call { };

// helper function; this overload handles the case that the call is not possible
template <typename Func>
invalid_call eval(Func f, ...) { return invalid_call{}; };

// bring in std::negation from C++17 to help create the below trait
template<class B>
struct negation : std::integral_constant<bool, !bool(B::value)> { };

// trait that determines whether `Func` can be invoked with an argument list of types `Args...`
template <typename Func, typename... Args>
using can_call = negation<std::is_same<decltype(eval(std::declval<Func>(), std::declval<Args>()...)), invalid_call>>;

// arbitary type that has no `operator+`
struct foo {};

int main()
{
    auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; };
    using FuncType = decltype(func);

    std::cout << "can call with (int, int): " << can_call<FuncType, int, int>::value << std::endl;
    std::cout << "can call with (foo, foo): " << can_call<FuncType, foo, foo>::value << std::endl;
}

这个例子可以正常工作。我喜欢的是必须声明 lambda 的繁琐方式:

auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; };

即必须指定尾随返回类型,因为C++14's deduced return types don't work with SFINAE。返回类型推导需要将参数列表类型替换为可调用的模板调用运算符,并且如果那里发生错误,则程序是非良构的。

理想情况下,我可以做到以下几点:

auto func = [](auto a1, auto a2) { return a1 + a2; };

并让返回类型自动运行;这将是提供给我的用户的最直观的界面。这是一个非常简单的示例,因此decltype() 的参数看起来不错,但实际上,lambda 可能是多个语句,这不适用于这种方法。所以我的问题是:

使用任何现代 C++ 技术(C++14 最好,但如果需要,我也愿意接受更新的功能),有什么方法可以在编译时测试通用 lambda 是否可以调用参数类型的任意列表?

【问题讨论】:

  • 似乎你的问题中有你的问题的答案:不,不是没有尾随返回类型。
  • @Barry:我认为可能是这样,但我的现代 C++ 知识并不权威,所以我想看看是否还有其他技巧。如果我受限于对尾随返回类型的需求,那么这种方法的用处就会大大降低,因为 lambda 不能有多个语句。
  • 由于返回类型推导需要您的 lambda 在每次推导时推导相同的返回类型,您可以只使用第一个返回语句(或最简单的)作为尾随返回类型 decltype。这不是真正的解决方案,但它减少了问题空间。

标签: c++ lambda c++14 template-meta-programming


【解决方案1】:

当然,使用宏的 C++98 特性

#define RETURNS(...) noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; }

然后

auto func = [](auto a1, auto a2) RETURNS(a1+a2);

会的。

我们自己的@Barry 提出了一个 C++20 提案来实现这一点

auto func = [](auto a1, auto a2) => a1+a2;

不使用宏。

一般来说,强制编译函数或 lambda 的主体以确定 SFINAE 表达式是否可接受是不可能的,也不打算这样做。这样的错误应该很难,因为这简化了 C++ 编译器的工作;他们不必能够编译任意函数的整个主体,然后在确定重载解析是否成功时干净地退回到无错误状态。

如果在 return 语句中使用了多个 return 语句或一长串复杂类型,那么你就不走运了。写decltype。祈祷你做对了。

【讨论】:

  • ...哎呀。投赞成票,我们不要再谈论它了:p
猜你喜欢
  • 2022-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-30
  • 2011-04-15
  • 1970-01-01
相关资源
最近更新 更多