Как SFINAE с использованием сигнатуры функции шаблона функции - PullRequest
1 голос
/ 14 июля 2020

У меня есть код, который принимает функцию и выполняет ее на основе сигнатуры функции, как показано ниже:

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

Я хотел бы расширить этот код, чтобы он мог принимать лямбда-функции, поскольку в реальном коде почти все агенты будут вызывать это с помощью лямбда-выражений GENERI C.

Я попытался заменить функторы на std::function, но это не удалось, так как лямбда - это не 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);
    }
}; 

это тоже не сработало, поскольку функции, которые выполняет исполнитель, ВСЕГДА являются шаблонными функциями (GENERI C Lambda). Я не знаю точно, как подойти к этой проблеме, любая помощь приветствуется.

решение c ++ 14, пожалуйста (я знаю, что invoke_result - это c ++ 17)

https://godbolt.org/z/W7z3Mv

Ответы [ 2 ]

2 голосов
/ 14 июля 2020

Исправить довольно просто. Во-первых, я бы использовал 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) {
    [...]
}

Это позволяет не-шаблонные ссылки на функции и общие c лямбды, но следующее пока не работает:

template <int N>
void f1(Value<N>& item){ [...] }

int main(){
    Executor exec;
    std::vector<Value<3>> vec(10);
    exec.do_exec(vec, f1);
}

Для меня это не срабатывает из-за довольно общей c ошибки вывода / замены аргументов шаблона. Чтобы это работало, вам необходимо указать f1 со значением N, например:

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

ОБНОВЛЕНИЕ для совместимости с 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 в приведенном выше решении. См. Полный пример в демонстрации, включая общие c лямбды.

Живая демонстрация для C ++ 14

2 голосов
/ 14 июля 2020

Извините, но ... шаблонная функция

template <int N>
void f1(Value<N>& item)
{
    for (auto& i : item.value) {
        i = 123;
    }
}

не объект, а набор объектов; поэтому вы не можете передать его другой функции в качестве аргумента

exec.do_exec(vec, f1);

То же самое для f2.

Но вы можете обернуть его внутри объекта (лямбда-функция - это синтаксис c сахар для этого типа решения)

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)

Ниже приведена модифицированная версия исходного примера проводника компилятора с парой вызовов do_exec() с общими c лямбда-выражениями .

#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; });
}
...