Параметры DescribeFunction с типами шаблонов Variadic - PullRequest
2 голосов
/ 30 мая 2019

Я хочу написать шаблонную функцию, которая принимает указатель на функцию и все, кроме последних 2 аргументов этой функции. Примерно так:

template <typename T, typename... ARGS>
void foo(void(*func)(ARGS..., T*, int*), ARGS... params);

Я бы хотел сделать что-то вроде следующего:

  1. Учитывая функцию подписи void bar(bool, char*, int*), я бы хотел позвонить: foo(bar, false)
  2. Учитывая функцию подписи void bar(bool, int*, int*), я бы хотел позвонить: foo(bar, false)
  3. Учитывая функцию подписи void bar(char*, int*), я бы хотел позвонить: foo(bar)

Но когда я пытаюсь определить foo, как это, я получаю ошибку:

ошибка C2672: foo: не найдена соответствующая перегруженная функция
ошибка C2784: void foo(void (__cdecl *)(ARGS...,T* ,int *),ARGS...): не удалось вывести аргумент шаблона для void (__cdecl* )(ARGS...,T *,int* ) из void (bool,char *,int* )

Что я могу сделать, чтобы помочь с удержанием?

Ответы [ 2 ]

2 голосов
/ 30 мая 2019

Причина, по которой это не работает, состоит в том, что ARGS... - это пакет с переменным числом, и при использовании для удержания его можно использовать только на конце сигнатуры функции. Например, вы можете вывести:

void(*)(int,Args...)

Но вы не можете вывести

void(*)(Args...,int)

Поскольку ваша проблема требует, чтобы аргумент последний был определенного типа, а второй-последний - для конкретного вывода, вам потребуется вывести всю сигнатуру функции func и использовать SFINAE для предотвратить случайный вызов этой функции с неверными аргументами.

Для этого нам сначала нужно извлечь последний параметр nth. Черта простого типа для этого может быть записана следующим образом:

#include <type_traits>

// A simple type-trait that gets the Nth type from a variadic pack
// If N is negative, it extracts from the opposite end of the pack
// (e.g. -1 == the last entry)
template<int N, bool IsPositive, typename...Args>
struct extract_nth_impl;

template<typename Arg0, typename...Args>
struct extract_nth_impl<0,true,Arg0,Args...> {
  using type = Arg0;
};

template<int N, typename Arg0, typename...Args>
struct extract_nth_impl<N,true,Arg0,Args...>
  : extract_nth_impl<N-1,true,Args...>{};

template<int N, typename...Args>
struct extract_nth_impl<N,false,Args...> {
  using type = typename extract_nth_impl<(sizeof...(Args)+N),true,Args...>::type;
};

// A type-trait wrapper to avoid the need for 'typename'
template<int N, typename...Args>
using extract_nth_t = typename extract_nth_impl<N,(N>=0),Args...>::type;

Мы можем использовать это, чтобы извлечь последний параметр, чтобы убедиться, что он int*, и второй, последний параметр, чтобы узнать его тип (T*). Тогда мы можем просто использовать std::enable_if для удаления любых неправильных входных данных, чтобы эта функция не компилировалась при неправильном использовании.

template<
  typename...Args,
  typename...UArgs,
  typename=std::enable_if_t<
    (sizeof...(Args) >= 2) &&
    (sizeof...(Args)-2)==(sizeof...(UArgs)) &&
    std::is_same_v<extract_nth_t<-1,Args...>,int*> &&
    std::is_pointer_v<extract_nth_t<-2,Args...>>
  >
>
void foo(void(*func)(Args...), UArgs&&...params)
{
    // your code here, e.g.:
    // bar(func, std::forward<UArgs>(params)...);
}

Примечание: Шаблон и подпись изменились следующими способами:

  1. Теперь у нас есть Args... и UArgs.... Это потому, что мы хотим захватить N типов аргументов для func, но нам нужны только N-2 аргументы для params
  2. Теперь мы сопоставим void(*func)(Args...) вместо void(*func)(Args...,T*,int*). T* больше не является template параметром
  3. У нас есть это длинное std::enable_if_t, которое используется, чтобы отстранить SFINAE от плохих случаев, таких как N<2, слишком много параметров для числа аргументов подписи, T* (второй-последний аргумент) не является указателем, и последняя подпись arg int*

Но в целом это работает. Если в определении функции был необходим T, вы можете легко извлечь его с помощью:

    using T = std::remove_point_t<extract_nth_t<-2,Args...>>;

(Примечание: remove_pointer_t используется для сопоставления только с типом, а не с указателем)

Следующие тесты работают для меня с использованием clang-8.0 и -std=c++17:

void example1(bool, char*, int*){}
void example2(bool, int*, int*){}
void example3(char*, int*){}
void example4(char*, char*){}

int main() {
  foo(&::example1,false);
  // foo(&::example1); -- fails to compile - too few arguments (correct)
  foo(&::example2,false);
  // foo(&::example2,false,5); -- fails to compile - too many arguments (correct)
  foo(&::example3);
  // foo(&::example4); -- fails to compile - last argument is not int* (correct)
}

Редактировать: Как указано @ max66, это решение не ограничивает конвертируемые типы в качестве входных данных для param. Это означает, что он может потерпеть неудачу, если любой param не является должным образом конвертируемым. Чтобы исправить это, можно добавить отдельное условие к std::enable_if, если это важный качественный атрибут API.

1 голос
/ 30 мая 2019

Мне кажется, что ты выглядишь следующим образом

template <std::size_t N, typename ...Ts>
using revType = std::tuple_element_t<sizeof...(Ts)-1u-N, std::tuple<Ts...>>;

template <typename ... As1, typename ... As2,
          typename T = revType<1u, As1...>,
          std::enable_if_t<std::is_same_v<
             std::tuple<As1...>, std::tuple<As2..., T, int*>>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
 {
 }

Как указывает Bitwize (спасибо), это решение имеет большие ограничения: требуется, чтобы аргументы foo() (типов As2...) выводились точно как соответствующие типы для As1.... Поэтому, если As1... начинаться с std::string, вы не можете передать "abc" в качестве первого As2... параметра, потому что "abc" - это char const [4], который является конвертируемым, но отличается от std::string.

Как предлагает сам Bitwize, вы можете избежать этой проблемы, используя std::is_convertible вместо std::is_same_v, по сравнению с кортежами, использующими std::tuple конструктор преобразования, поэтому

template <typename ... As1, typename ... As2,
          typename T = revType<1u, As1...>,
          std::enable_if_t<std::is_convertible_v<
             std::tuple<As1...>, std::tuple<As2..., T, int*>>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
 {
 }

В этом случае, если вы хотите быть уверенным, что последний тип As1... равен точно int * (и не только конвертируемый), вы можете добавить чек

template <typename ... As1, typename ... As2,
          typename T = revType<1u, As1...>,
          typename U = revType<0u, As1...>,
          std::enable_if_t<
                std::is_convertible_v<std::tuple<As1...>,
                                      std::tuple<As2..., T, int*>>
             && std::is_same_v<int *, U>, int> = 0>
void foo(void(*f)(As1...), As2 ... as)
 {
 }
...