【问题标题】:enable_if-like SFINAE expression for std::apply用于 std::apply 的类似 enable_if 的 SFINAE 表达式
【发布时间】:2020-07-20 03:18:56
【问题描述】:

如果可调用模板参数不能通过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。正如预期的那样,我在实例化站点只收到一个错误。

如果在 std::tuple 中指定了 Args,我正在尝试找到禁用函数的等效项,以便与 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) {})

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

当然,在这个非通用示例中,仅使用 enable_if_callable&lt;Fn, int, float&gt; 就可以了,但是当元组可以包含任意数量的元素时,我会尝试使其工作。

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

【问题讨论】:

  • 感谢收看。我尝试稍微改写一下,并将示例函数调整为具体而不是通用,希望使其更易于理解。

标签: c++ variadic-templates sfinae stdtuple


【解决方案1】:

您可以使用 中的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

【讨论】:

  • 这仅在您有权访问class ... Args 时有效(即元组是模板声明的一部分)。这相当于我的enable_if_callable 所做的。基本上,如果 enable_if 表达式的参数是整个元组类型 (T = std::tuple&lt;Args...&gt;) 而不是元组元素 (Args... = Ts...),我正在寻找的东西是有效的。我确实找到了一个解决方案,即创建一个apply,使返回类型成为声明的一部分。
  • 虽然(helper,不是apply_fn)和apply_fn 会错误地参与重载解决,但这会生成从 API 中删除的一级错误。我的目标是,当人们错误地使用这个 API 时,他们得到的错误是在他们的代码中,而不是在我的标题中。
【解决方案2】:

没有必要重新发明轮子。只需使用@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>()));

【讨论】:

  • 这是一个很好的解决方案,尽管它与@asmmo 的不太一样。但是,就我而言,我的目标是 C++14,所以无论如何我都需要自己实现 apply :) (我确实检查了带有 C++17 的 std::apply 在这种情况下也不起作用,因为它也没有尾随返回类型)。
【解决方案3】:

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

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>()));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    相关资源
    最近更新 更多