Рассмотрим следующее (которое не компилируется, но мы исправим это через мгновение):
void foo(const int *n) { }
template <typename ...Args>
void bar(void (*func)(Args...), Args... args) { func(args...); }
int main(int argc, char *argv[])
{
int n = 42;
bar(foo, &n);
}
Шаблонная функция bar()
принимает указатель на функцию для вызова и набор аргументов для передачи ей. gcc 7.4.0 диагностирует следующую ошибку:
test.cpp:6:6: note: template argument deduction/substitution failed:
test.cpp:11:16: note: inconsistent parameter pack deduction with ‘const int*’ and ‘int*’
Таким образом, ясно, что правила вывода типа недостаточно расслаблены, чтобы можно было вывести const T*
, когда наблюдаются как const T*
, так и T*
. Хорошо. Это достаточно просто для исправить с помощью гипса:
bar(foo, static_cast<const int *>(&n));
Но это некрасиво. С++ 17 имеет std::as_const()
, что делает его немного менее уродливым (&std::as_const(n)
), но в моем текущем проекте я ограничен С++ 14, грустное лицо.
Вопрос: Есть ли способ изменить этот код так, чтобы вывод типа выполнялся успешно без явного указания параметров шаблона для bar()
и без приведения для разрешения неоднозначной константности? Нестандартное мышление разрешено, если я могу передать указатель функции и его аргументы в функцию-шаблон!
@vahancho В моем реальном случае использования n
является переменной struct
и не может быть const
. Я, очевидно, могу создать отдельную const
ссылку на него, но суть вопроса в том, чтобы не уходить с дороги таким образом.
Вам понадобятся два пакета параметров. Один для вывода параметров для передаваемой функции и второй пакет параметров для пересылаемых аргументов. SFINAE можно использовать для предотвращения нежелательного разрешения перегрузки вместо ошибки компиляции в случае недопустимых аргументов. P.S. Ваша «не компилируемая» версия (в самом вопросе) фактически компилируется, потому что она идентична версии, которая компилируется.
@SamVarshavchik Спасибо, хорошее предложение, и я «исправил» (или, скорее, сломал, хе-хе) начальный фрагмент кода в сообщении.
Вы можете разделить вывод для указателя функции и параметров:
void foo(const int *n) {}
template <typename... FArgs, typename... Args>
void bar(void (*func)(FArgs...), Args&&... args) {
func(std::forward<Args>(args)...);
}
int main(int argc, char *argv[]) {
int n = 42;
bar(foo, &n);
}
Но в этот момент я задаюсь вопросом, почему указатель функции должен быть разложен. Почему бы не принять любой callable?
void foo(const int *n) {}
template <typename F, typename... Args>
void bar(F func, Args&&... args) {
func(std::forward<Args>(args)...);
}
int main(int argc, char *argv[]) {
int n = 42;
bar(foo, static_cast<const int *>(&n));
bar([](int const*){}, &n);
}
Также помните, что C++17 предлагает std::invoke
:
std::invoke(foo, &n);
Вместо этого вы можете определить
const int n = 42; bar(foo, &n);
.