Ошибка вывода аргумента шаблона из-за несогласованной константности

Рассмотрим следующее (которое не компилируется, но мы исправим это через мгновение):

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() и без приведения для разрешения неоднозначной константности? Нестандартное мышление разрешено, если я могу передать указатель функции и его аргументы в функцию-шаблон!

Вместо этого вы можете определить const int n = 42; bar(foo, &n);.

vahancho 13.06.2019 15:54

@vahancho В моем реальном случае использования n является переменной struct и не может быть const. Я, очевидно, могу создать отдельную const ссылку на него, но суть вопроса в том, чтобы не уходить с дороги таким образом.

TypeIA 13.06.2019 15:55

Вам понадобятся два пакета параметров. Один для вывода параметров для передаваемой функции и второй пакет параметров для пересылаемых аргументов. SFINAE можно использовать для предотвращения нежелательного разрешения перегрузки вместо ошибки компиляции в случае недопустимых аргументов. P.S. Ваша «не компилируемая» версия (в самом вопросе) фактически компилируется, потому что она идентична версии, которая компилируется.

Sam Varshavchik 13.06.2019 15:56

@SamVarshavchik Спасибо, хорошее предложение, и я «исправил» (или, скорее, сломал, хе-хе) начальный фрагмент кода в сообщении.

TypeIA 13.06.2019 16:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
113
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете разделить вывод для указателя функции и параметров:

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

Другие вопросы по теме