简体   繁体   English

如何使用模板 function 的 function 签名进行 SFINAE

[英]How to SFINAE using the function signature of a template function

I have a code that takes a function and executes it based on the function signature like below:我有一个代码,它采用 function 并根据 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);
    }
};

when the user passes in one of the following functions, the Executor run the do_exec() that matches its signatures.当用户传入以下函数之一时, 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);
}

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.我想扩展这段代码,所以它可以使用 lambda 函数,因为在实际代码中,几乎所有代理都会使用 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.我尝试用std::function替换函子,但它失败了,因为 lambda 不是std::function并没有真正发生。

then I tried to take two template arguments and SFINAE out the one that doesn't match the signature like below:然后我尝试将两个模板 arguments 和 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);
    }
}; 

this didn't work either, since the functions that executor will take are ALWAYS template functions (GENERIC Lambda).这也不起作用,因为执行程序将采用的函数始终是模板函数(通用 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)请 c++14 解决方案(我知道 invoke_result 是 c++ 17)

https://godbolt.org/z/W7z3Mv https://godbolt.org/z/W7z3Mv

Sorry but... a template function对不起,但是...模板 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;不是 object 而是一组 object; so you can't pass it to a another function as argument所以你不能将它作为参数传递给另一个 function

exec.do_exec(vec, f1);

The same for f2 . f2也是如此。

But you can wrap it inside an object (a lambda function is syntactic sugar for this type of solution)但是您可以将其包装在 object 中(lambda function 是此类解决方案的语法糖)

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所以可以发送全套function如下

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)这应该有效(但不是您在编译器资源管理器中注释的Executor示例,因为第一个do_exec()未启用/禁用 SFINAE)

The following is a modified version of you original compiler explorer example with a couple of calls to do_exec() with generic lambdas.以下是原始编译器资源管理器示例的修改版本,其中使用通用 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; });
}

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.首先,我将使用类型特征库中的std::is_invocable_v来测试 SFINAE 机制中兼容的 function 签名。 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:这允许对函数和通用 lambda 的非模板引用,但以下内容尚无法使用:

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:要完成这项工作,您需要使用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)
}

Live Demo现场演示


UPDATE for C++14-compatibility更新 C++14 兼容性

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):由于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");

This can be used as a drop-in replacement for std::is_invocable_v in the above solution.这可以用作上述解决方案中std::is_invocable_v的直接替代品。 See the demo for the full example, including generic lambdas.有关完整示例,请参阅演示,包括通用 lambda。

Live Demo for C++14 C++14 的现场演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM