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
}
如果你需要像评论说的那样将你的函数限制在一个内部元组中,你可以同时使用以下两个模板
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));
}
无需重新发明轮子。只需使用@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>()));
如果可调用模板参数无法通过 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
}
如果你需要像评论说的那样将你的函数限制在一个内部元组中,你可以同时使用以下两个模板
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));
}
无需重新发明轮子。只需使用@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>()));