Причина, по которой это не работает, состоит в том, что 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)...);
}
Примечание: Шаблон и подпись изменились следующими способами:
- Теперь у нас есть
Args...
и UArgs...
. Это потому, что мы хотим захватить N типов аргументов для func
, но нам нужны только N-2
аргументы для params
- Теперь мы сопоставим
void(*func)(Args...)
вместо void(*func)(Args...,T*,int*)
. T*
больше не является template
параметром
- У нас есть это длинное
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.