如何使用模板函数的函数签名进行 SFINAE
How to SFINAE using the function signature of a template function
我有一个代码接受一个函数并根据函数签名执行它,如下所示:
template <int Num>
struct Value {
int value[Num];
};
struct Executor {
template <int N>
void do_exec(std::vector<Value<N>>& n, void (&func) (Value<N>&)) {
for (auto& item : n)
func(item);
}
template <int N>
void do_exec(std::vector<Value<N>>& n, void (&func) (Value<N>&, int)) {
for (int i = 0; i != n.size(); i++)
func(n[i], i);
}
};
当用户传入以下函数之一时,Executor
运行 匹配其签名的do_exec()
。
template <int N>
void f1(Value<N>& item)
{
for (auto& i : item.value) {
i = 123;
}
}
template <int N>
void f2(Value<N>& item, int d)
{
for (auto& i : item.value) {
i = d;
}
}
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1);
}
我想扩展此代码,使其可以采用 lambda 函数,因为在实际代码中,几乎所有代理都会使用 GENERIC lambda 调用它。
我尝试用 std::function
替换仿函数,但它失败了,因为 lambda 不是 std::function
并且类型推导并没有真正发生。
然后我尝试取出两个模板参数和 SFINAE 中与签名不匹配的参数,如下所示:
template <typename Fn, typename T, typename = void>
struct HasIndex : std::false_type {};
template <typename Fn, typename T>
struct HasIndex<Fn, T, std::void_t<std::invoke_result_t<Fn, T&, int>>> : std::true_type {};
struct Executor {
template <int N, typename Fn, std::enable_if_t<!HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto& item : n)
func(item);
}
template <int N, typename Fn, std::enable_if_t<HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (int i = 0; i != n.size(); i++)
func(n[i], i);
}
};
这也不起作用,因为执行程序将采用的函数始终是模板函数 (GENERIC Lambda)。我不知道如何解决这个问题,感谢任何帮助。
c++14 解决方案(我知道 invoke_result 是 c++17)
抱歉,但是...模板函数
template <int N>
void f1(Value<N>& item)
{
for (auto& i : item.value) {
i = 123;
}
}
不是一个对象而是一组对象;所以你不能将它作为参数传递给另一个函数
exec.do_exec(vec, f1);
f2
也一样。
但您可以将其包装在一个对象中(lambda 函数是此类解决方案的语法糖)
struct foo_1
{
template <int N>
void operator() (Value<N>& item)
{
for (auto& i : item.value)
i = 123;
}
};
struct foo_2
{
template <int N>
void operator() (Value<N>& item, int d)
{
for (auto& i : item.value)
i = d;
}
};
这样就可以发全套功能如下
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
foo_1 f1;
foo_2 f2;
exec.do_exec(vec, f1);
exec.do_exec(vec, f2);
}
这应该有效(但不是您在编译器资源管理器中评论的 Executor
示例,因为第一个 do_exec()
不是 SFINAE enabled/disabled)
以下是原始编译器资源管理器示例的修改版本,其中使用通用 lambda 对 do_exec()
进行了几次调用。
#include <functional>
#include <iostream>
#include <numeric>
#include <type_traits>
#include <vector>
#include <array>
template <int Num>
struct Value {
std::array<int, Num> value;
};
template <typename Fn, typename T, typename = void>
struct HasIndex : std::false_type {};
template <typename Fn, typename T>
struct HasIndex<Fn, T, std::void_t<std::invoke_result_t<Fn, T&, int>>> : std::true_type {};
struct Executor {
template <int N, typename Fn,
std::enable_if_t<!HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto& item : n)
func(item);
}
template <int N, typename Fn,
std::enable_if_t<HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto i = 0u; i != n.size(); i++)
func(n[i], int(i));
}
};
struct foo_1
{
template <int N>
void operator() (Value<N>& item)
{
for (auto& i : item.value)
i = 123;
}
};
struct foo_2
{
template <int N>
void operator() (Value<N>& item, int d)
{
for (auto& i : item.value)
i = d;
}
};
template <int N>
void read(const Value<N>& item)
{
for (auto& i : item.value) {
std::cout << i << " ";
}
}
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
foo_1 f1;
foo_2 f2;
exec.do_exec(vec, f1);
exec.do_exec(vec, f2);
exec.do_exec(vec, [](auto & item)
{ for ( auto & i : item.value ) std::cout << i << std::endl; });
exec.do_exec(vec, [](auto & item, int d)
{ for (auto& i : item.value) i = d; });
}
修复相当简单。首先,我将使用类型特征库中的 std::is_invocable_v
来测试 SFINAE 机制中的兼容函数签名。换行符使模板签名可读,我发现:
template<
int N,
typename Fn,
std::enable_if_t<std::is_invocable_v<Fn, Value<N>&>>* = nullptr
>
void do_exec(std::vector<Value<N>>& n, Fn func) {
[...]
}
template<
int N,
typename Fn,
std::enable_if_t<std::is_invocable_v<Fn, Value<N>&, int>>* = nullptr
>
void do_exec(std::vector<Value<N>>& n, Fn func) {
[...]
}
这允许对函数和通用 lambda 的非模板引用,但以下内容尚不可用:
template <int N>
void f1(Value<N>& item){ [...] }
int main(){
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1);
}
对我来说,这失败了一个非常通用的模板参数 deduction/substitution 失败。要完成这项工作,您需要使用 N
的值专门化 f1
,如:
int main(){
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1<3>); // Fn is deduced as void(&)(Value<3>&) (I think)
}
更新 C++14 兼容性
由于 std::is_invocable_v
仅在 C++17 之后可用,您可以使用如下解决方法(未经过彻底测试,但我感觉很好):
template<typename F, typename ArgsTuple, typename Enable = void>
struct my_is_invocable_impl : std::false_type {};
template<typename F, typename... Args>
struct my_is_invocable_impl<
F,
std::tuple<Args...>,
decltype(std::declval<F>()(std::declval<Args>()...))
> : std::true_type {};
template<typename T, typename... Args>
constexpr bool my_is_invocable = my_is_invocable_impl<T, std::tuple<Args...>>::value;
// Some test cases
static_assert(my_is_invocable<void(*)(int, double), int, double>, "Oops");
static_assert(my_is_invocable<void(*)(void*), void*>, "Oops");
static_assert(my_is_invocable<void(*)()>, "Oops");
static_assert(!my_is_invocable<void(*)(int, double)>, "Oops");
static_assert(!my_is_invocable<void(*)(void*)>, "Oops");
这可以用作上述解决方案中 std::is_invocable_v
的直接替代。请参阅演示以获取完整示例,包括通用 lambda。
我有一个代码接受一个函数并根据函数签名执行它,如下所示:
template <int Num>
struct Value {
int value[Num];
};
struct Executor {
template <int N>
void do_exec(std::vector<Value<N>>& n, void (&func) (Value<N>&)) {
for (auto& item : n)
func(item);
}
template <int N>
void do_exec(std::vector<Value<N>>& n, void (&func) (Value<N>&, int)) {
for (int i = 0; i != n.size(); i++)
func(n[i], i);
}
};
当用户传入以下函数之一时,Executor
运行 匹配其签名的do_exec()
。
template <int N>
void f1(Value<N>& item)
{
for (auto& i : item.value) {
i = 123;
}
}
template <int N>
void f2(Value<N>& item, int d)
{
for (auto& i : item.value) {
i = d;
}
}
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1);
}
我想扩展此代码,使其可以采用 lambda 函数,因为在实际代码中,几乎所有代理都会使用 GENERIC lambda 调用它。
我尝试用 std::function
替换仿函数,但它失败了,因为 lambda 不是 std::function
并且类型推导并没有真正发生。
然后我尝试取出两个模板参数和 SFINAE 中与签名不匹配的参数,如下所示:
template <typename Fn, typename T, typename = void>
struct HasIndex : std::false_type {};
template <typename Fn, typename T>
struct HasIndex<Fn, T, std::void_t<std::invoke_result_t<Fn, T&, int>>> : std::true_type {};
struct Executor {
template <int N, typename Fn, std::enable_if_t<!HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto& item : n)
func(item);
}
template <int N, typename Fn, std::enable_if_t<HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (int i = 0; i != n.size(); i++)
func(n[i], i);
}
};
这也不起作用,因为执行程序将采用的函数始终是模板函数 (GENERIC Lambda)。我不知道如何解决这个问题,感谢任何帮助。
c++14 解决方案(我知道 invoke_result 是 c++17)
抱歉,但是...模板函数
template <int N>
void f1(Value<N>& item)
{
for (auto& i : item.value) {
i = 123;
}
}
不是一个对象而是一组对象;所以你不能将它作为参数传递给另一个函数
exec.do_exec(vec, f1);
f2
也一样。
但您可以将其包装在一个对象中(lambda 函数是此类解决方案的语法糖)
struct foo_1
{
template <int N>
void operator() (Value<N>& item)
{
for (auto& i : item.value)
i = 123;
}
};
struct foo_2
{
template <int N>
void operator() (Value<N>& item, int d)
{
for (auto& i : item.value)
i = d;
}
};
这样就可以发全套功能如下
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
foo_1 f1;
foo_2 f2;
exec.do_exec(vec, f1);
exec.do_exec(vec, f2);
}
这应该有效(但不是您在编译器资源管理器中评论的 Executor
示例,因为第一个 do_exec()
不是 SFINAE enabled/disabled)
以下是原始编译器资源管理器示例的修改版本,其中使用通用 lambda 对 do_exec()
进行了几次调用。
#include <functional>
#include <iostream>
#include <numeric>
#include <type_traits>
#include <vector>
#include <array>
template <int Num>
struct Value {
std::array<int, Num> value;
};
template <typename Fn, typename T, typename = void>
struct HasIndex : std::false_type {};
template <typename Fn, typename T>
struct HasIndex<Fn, T, std::void_t<std::invoke_result_t<Fn, T&, int>>> : std::true_type {};
struct Executor {
template <int N, typename Fn,
std::enable_if_t<!HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto& item : n)
func(item);
}
template <int N, typename Fn,
std::enable_if_t<HasIndex<Fn, Value<N>>::value, int> = 1>
void do_exec(std::vector<Value<N>>& n, Fn func) {
for (auto i = 0u; i != n.size(); i++)
func(n[i], int(i));
}
};
struct foo_1
{
template <int N>
void operator() (Value<N>& item)
{
for (auto& i : item.value)
i = 123;
}
};
struct foo_2
{
template <int N>
void operator() (Value<N>& item, int d)
{
for (auto& i : item.value)
i = d;
}
};
template <int N>
void read(const Value<N>& item)
{
for (auto& i : item.value) {
std::cout << i << " ";
}
}
int main()
{
Executor exec;
std::vector<Value<3>> vec(10);
foo_1 f1;
foo_2 f2;
exec.do_exec(vec, f1);
exec.do_exec(vec, f2);
exec.do_exec(vec, [](auto & item)
{ for ( auto & i : item.value ) std::cout << i << std::endl; });
exec.do_exec(vec, [](auto & item, int d)
{ for (auto& i : item.value) i = d; });
}
修复相当简单。首先,我将使用类型特征库中的 std::is_invocable_v
来测试 SFINAE 机制中的兼容函数签名。换行符使模板签名可读,我发现:
template<
int N,
typename Fn,
std::enable_if_t<std::is_invocable_v<Fn, Value<N>&>>* = nullptr
>
void do_exec(std::vector<Value<N>>& n, Fn func) {
[...]
}
template<
int N,
typename Fn,
std::enable_if_t<std::is_invocable_v<Fn, Value<N>&, int>>* = nullptr
>
void do_exec(std::vector<Value<N>>& n, Fn func) {
[...]
}
这允许对函数和通用 lambda 的非模板引用,但以下内容尚不可用:
template <int N>
void f1(Value<N>& item){ [...] }
int main(){
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1);
}
对我来说,这失败了一个非常通用的模板参数 deduction/substitution 失败。要完成这项工作,您需要使用 N
的值专门化 f1
,如:
int main(){
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1<3>); // Fn is deduced as void(&)(Value<3>&) (I think)
}
更新 C++14 兼容性
由于 std::is_invocable_v
仅在 C++17 之后可用,您可以使用如下解决方法(未经过彻底测试,但我感觉很好):
template<typename F, typename ArgsTuple, typename Enable = void>
struct my_is_invocable_impl : std::false_type {};
template<typename F, typename... Args>
struct my_is_invocable_impl<
F,
std::tuple<Args...>,
decltype(std::declval<F>()(std::declval<Args>()...))
> : std::true_type {};
template<typename T, typename... Args>
constexpr bool my_is_invocable = my_is_invocable_impl<T, std::tuple<Args...>>::value;
// Some test cases
static_assert(my_is_invocable<void(*)(int, double), int, double>, "Oops");
static_assert(my_is_invocable<void(*)(void*), void*>, "Oops");
static_assert(my_is_invocable<void(*)()>, "Oops");
static_assert(!my_is_invocable<void(*)(int, double)>, "Oops");
static_assert(!my_is_invocable<void(*)(void*)>, "Oops");
这可以用作上述解决方案中 std::is_invocable_v
的直接替代。请参阅演示以获取完整示例,包括通用 lambda。