Помощь компилятору С++ в разрешении перегрузки функций
В некоторых случаях компиляторы С++ не могут выбрать подходящую перегружаемую функцию, например, в очевидной с человеческой точки зрения ситуации — возникает ошибка компиляции:
Проблема – в отсутствии параметров у f в последней строке, по которым компилятор мог бы произвести разрешение перегрузки. То, что параметрами for_each передаются итераторы вполне определенного типа – для компилятора не имеет значения.
Можно предложить несколько «лобовых» способов решения проблемы:
1) Использование static_cast<>() для принудительного приведения к указателю на функцию нужного типа.
Приводим к указателю на void f(int i):
Приводим к указателю на void f(string i):
2) Определение указателей на функции и их использование.
Используем void f(int i):
Используем void f(string i):
3) Создание явных специализаций для шаблонной функции for_each:
Используем void f(int i):
Используем void f(string i):
или более компактно:
Предложенные выше способы не универсальны и приводят к необходимости разработки на вызывающей стороне дополнительного кода. Лучшей идеей является создание над функцией f тонкой оболочки, принимающей параметры функции f и передающей их в нее без изменений.
Необходимо также учитывать возможности:
Поэтапно разберем построение такой оболочки. Лямбда-функция – хорошая основа. При поддержке компилятором стандарта С++11 это можно реализовать так:
или с использованием decltype():
При поддержке компилятором стандарта С++14 имеем для полиморфной лямбды следующее решение:
Итак, мы передаем параметром в лямбду параметр функции f, и потом вызываем f с этим параметром. Но тут есть проблема: мы не знаем заранее – являются параметры функции f значениями или ссылками? Поэтому необходимо использовать идеальную передачу.
По теме идеальной передачи (perfect forwarding) можно прочитать у Мейерса [1]. Более понятный (для меня во всяком случае) вариант изложения того же материала можно найти в статье[2], перевод которой есть на Хабре [3].
Для неопределенного числа параметров функции указываем вариативными параметры лямбды и вызываем std::forward для каждого передаваемого в f параметра.
Добавляем спецификатор времени компиляции noexcept[4] и оператор noexcept[5] для указания компилятору – будет ли функция f выбрасывать исключения[6].
Добавляем вывод типа возвращаемого значения для конструируемой лямбды.
Если раздражает трехкратное повторение кода и не гнушаемся использовать макросы – можно уменьшить размер на вызывающей стороне.
В итоге имеем оболочку, которая помогает компилятору разрешить перегружаемую функцию.
[1] S.Meyers “Effective modern C++” Item 24: Distinguish universal references from rvalue references.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/ru/post/242639
[4] en.cppreference.com/w/cpp/language/noexcept_spec
[5] en.cppreference.com/w/cpp/language/noexcept
[6] habr.com/ru/post/164221
[7] www.fluentcpp.com/2017/08/01/overloaded-functions-stl
[8] www.youtube.com/watch?v=I3T4lePH-yA
Проблема – в отсутствии параметров у f в последней строке, по которым компилятор мог бы произвести разрешение перегрузки. То, что параметрами for_each передаются итераторы вполне определенного типа – для компилятора не имеет значения.
Можно предложить несколько «лобовых» способов решения проблемы:
1) Использование static_cast<>() для принудительного приведения к указателю на функцию нужного типа.
Приводим к указателю на void f(int i):
Приводим к указателю на void f(string i):
2) Определение указателей на функции и их использование.
Используем void f(int i):
Используем void f(string i):
3) Создание явных специализаций для шаблонной функции for_each:
Используем void f(int i):
Используем void f(string i):
или более компактно:
Предложенные выше способы не универсальны и приводят к необходимости разработки на вызывающей стороне дополнительного кода. Лучшей идеей является создание над функцией f тонкой оболочки, принимающей параметры функции f и передающей их в нее без изменений.
Необходимо также учитывать возможности:
- существования неопределенного количества параметров у функции f,
- наличия возвращаемого значения функции,
- передачи параметров по ссылке,
- существования cv-квалификаторы параметров,
- возникновения исключений в функции f.
Поэтапно разберем построение такой оболочки. Лямбда-функция – хорошая основа. При поддержке компилятором стандарта С++11 это можно реализовать так:
или с использованием decltype():
При поддержке компилятором стандарта С++14 имеем для полиморфной лямбды следующее решение:
Итак, мы передаем параметром в лямбду параметр функции f, и потом вызываем f с этим параметром. Но тут есть проблема: мы не знаем заранее – являются параметры функции f значениями или ссылками? Поэтому необходимо использовать идеальную передачу.
По теме идеальной передачи (perfect forwarding) можно прочитать у Мейерса [1]. Более понятный (для меня во всяком случае) вариант изложения того же материала можно найти в статье[2], перевод которой есть на Хабре [3].
Для неопределенного числа параметров функции указываем вариативными параметры лямбды и вызываем std::forward для каждого передаваемого в f параметра.
Добавляем спецификатор времени компиляции noexcept[4] и оператор noexcept[5] для указания компилятору – будет ли функция f выбрасывать исключения[6].
Добавляем вывод типа возвращаемого значения для конструируемой лямбды.
Если раздражает трехкратное повторение кода и не гнушаемся использовать макросы – можно уменьшить размер на вызывающей стороне.
В итоге имеем оболочку, которая помогает компилятору разрешить перегружаемую функцию.
[1] S.Meyers “Effective modern C++” Item 24: Distinguish universal references from rvalue references.
[2] eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c
[3] habr.com/ru/post/242639
[4] en.cppreference.com/w/cpp/language/noexcept_spec
[5] en.cppreference.com/w/cpp/language/noexcept
[6] habr.com/ru/post/164221
[7] www.fluentcpp.com/2017/08/01/overloaded-functions-stl
[8] www.youtube.com/watch?v=I3T4lePH-yA
Комментарии
Отправить комментарий