Reputation: 1386
I have a code that takes a function and executes it based on the function signature like below:
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);
}
};
when the user passes in one of the following functions, the Executor
run the do_exec()
that matches its signatures.
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);
}
I would like to extend this code, so it can take lambda functions since in the real code, almost all of the agents will call this with GENERIC lambdas.
I tried replacing the functors with std::function
, but it failed since lambda is not a std::function
and type deduction didn't really happen.
then I tried to take two template arguments and SFINAE out the one that doesn't match the signature like below:
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);
}
};
this didn't work either, since the functions that executor will take are ALWAYS template functions (GENERIC Lambda). I don't know exactly how to approach this problem, any help appreciated.
c++14 solution please (I know invoke_result is c++ 17)
Upvotes: 1
Views: 617
Reputation: 7212
The fix is fairly simple. Firstly, I would use std::is_invocable_v
from the type traits library to test for compatible function signatures in the SFINAE mechanism. Line breaks keep the template signatures readable, I find:
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) {
[...]
}
This allows non-template references to functions and generic lambdas, but the following won't yet work:
template <int N>
void f1(Value<N>& item){ [...] }
int main(){
Executor exec;
std::vector<Value<3>> vec(10);
exec.do_exec(vec, f1);
}
For me, this fails with a pretty generic template argument deduction/substitution failure. To make this work, you need to specialize f1
with a value for N
, as in:
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)
}
UPDATE for C++14-compatibility
Since std::is_invocable_v
is only available after C++17, you can use a workaround like the following (not thoroughly tested, but I feel good about it):
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");
This can be used as a drop-in replacement for std::is_invocable_v
in the above solution. See the demo for the full example, including generic lambdas.
Upvotes: 2
Reputation: 66210
Sorry but... a template function
template <int N>
void f1(Value<N>& item)
{
for (auto& i : item.value) {
i = 123;
}
}
isn't an object but a set of object; so you can't pass it to a another function as argument
exec.do_exec(vec, f1);
The same for f2
.
But you can wrap it inside an object (a lambda function is syntactic sugar for this type of solution)
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;
}
};
so you can send the full set of function as follows
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);
}
This should works (but not your commented Executor
example in compiler explorer because the first do_exec()
isn't SFINAE enabled/disabled)
The following is a modified version of you original compiler explorer example with a couple of calls to do_exec()
with generic lambdas.
#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; });
}
Upvotes: 2