有没有办法在编译时检测是否可以使用给定的参数类型成功调用泛型lambda?

时间:2017-05-09 17:26:22

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

我希望能够在编译时确定给定一个通用的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任意参数类型列表?

1 个答案:

答案 0 :(得分:3)

当然,使用宏的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。祈祷你做对了。