enable_if-like std::apply 的 SFINAE 表达式

enable_if-like SFINAE expression for std::apply

如果可调用模板参数无法通过 std::apply 调用,我希望能够禁用函数。

也许从有效的地方开始解释问题会有所帮助。我有这个用于常规函数调用案例:

template <class Fn, class... Args>
using enable_if_callable = decltype(std::declval<Fn>()(std::declval<Args>()...));

...

template <class Fn, class = enable_if_callable<Fn, int, float>>
void call_fn(Fn fn) {
  fn(1, 2.0f);
}

// The above is correctly disabled in this case:
call_fn([](int) {})

当无法使用参数 (int, float) 调用 fn 时,上面的代码可以很好地禁用 call_fn。正如预期的那样,我在实例化站点只遇到一个错误。

如果 Args 在 std::tuple 中指定,我正在尝试找到禁用函数的等效项,以便与 std::apply:

一起使用
template <class Fn, class TupleArgs>
using enable_if_applicable = 
    decltype(std::apply(std::declval<Fn>(), std::declval<TupleArgs>()));

...

template <class Fn, class = enable_if_applicable<Fn, std::tuple<int, float>>>
void apply_fn(Fn fn) {
  std::apply(fn, std::make_tuple(1, 2.0f));
}

// The above is not correctly disabled in this case:
apply_fn([](int) {})

这不会阻止实例化,至少不会避免应用程序内部的错误,大概是因为这些错误不是来自声明的替换失败。我熟悉的分离元组的技术似乎不适用于此处(索引序列等)。

当然,在这个非一般性示例中,仅使用 enable_if_callable<Fn, int, float> 就可以了,但是当元组可以包含任意数量的元素时,我正在尝试使其工作。

关于如何实现这样的事情有什么想法吗?

当然,我在发布后几个小时就找到了解决方案。关键是实现我自己的 apply 版本,它使用尾随 return 类型,这导致模板替换失败,而不是实例化。

template <class Fn, class Args, size_t... Is>
auto apply(Fn&& fn, const Args& args, index_sequence<Is...>)
    -> decltype(fn(std::get<Is>(args)...)) {
  return fn(std::get<Is>(args)...);
}
template <class Fn, class... Args>
auto apply(Fn&& fn, const std::tuple<Args...>& args)
    -> decltype(apply(fn, args, make_index_sequence<sizeof...(Args)>())) {
  return apply(fn, args, make_index_sequence<sizeof...(Args)>());
}

template <class Fn, class Args>
using enable_if_applicable = decltype(apply(std::declval<Fn>(), std::declval<Args>()));

您可以使用 中的 std::is_invocable,如下所示

#include <tuple>
#include <type_traits>
#include <iostream>

template <class Fn, class ... Args, class = std::enable_if_t<std::is_invocable_v<Fn, Args...>>>
void apply_fn(Fn&& fn, const std::tuple<Args...>& t) {
    std::apply(std::forward<Fn>(fn), t);
}

int main() {
    apply_fn([ ](int el1,float el2) {std::cout << el1 << " " << el2;}, std::make_tuple(1,2.3f));//compiles
    //apply_fn([](int ) {},std::make_tuple(1,2.3f));//doesn't compile
}

Live

如果你需要像评论说的那样将你的函数限制在一个内部元组中,你可以同时使用以下两个模板

template <class Fn, class ... Args, class = std::enable_if_t<std::is_invocable_v<Fn, Args...>>>
void helper(Fn&& fn, const std::tuple<Args...>& t) {
    std::apply(std::forward<Fn>(fn), t);
}

template <class Fn>
void apply_fn(Fn&& fn) {
    helper(std::forward<Fn>(fn), std::make_tuple(1,2.3f));
}

Live

无需重新发明轮子。只需使用@asmmo 所说的:

template <class Fn, class ... Args, std::enable_if_t<std::is_invocable_v<Fn, Args...>, int> = 0>
void apply_fn(Fn&& fn, const std::tuple<Args...>& t) {
    std::apply(std::forward<Fn>(fn), t);
}

template<class Fn, class Tuple>
using enable_if_applicable = decltype(apply_fn(std::declval<Fn>(), std::declval<Tuple>()));