Функция std:: не может отличить перегруженные функции
Я пытаюсь понять, почему std::function
не может отличить перегруженные функции.
#include <functional>
void add(int,int){}
class A {};
void add (A, A){}
int main(){
std::function <void(int, int)> func = add;
}
В приведенном выше коде function<void(int, int)>
может соответствовать только одной из этих функций, и все же он терпит неудачу. Почему это так? Я знаю, что могу обойти это, используя лямбда или указатель на функцию, а затем сохранит функцию функции в функции. Но почему это не удается? Разве не ясно, какая функция я хочу выбрать? Пожалуйста, помогите мне понять, почему это не удается, поскольку я не могу понять, почему в этом случае сбой шаблона не работает.
Ошибки компилятора, которые я использую для этого, следующие:
test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to
'std::function<void (int, int)>'
std::function <void(int, int)> func = add;
^ ~~~
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note:
candidate constructor not viable: no overload of 'add' matching
'std::__1::nullptr_t' for 1st argument
_LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {}
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note:
candidate constructor not viable: no overload of 'add' matching 'const
std::__1::function<void (int, int)> &' for 1st argument
function(const function&);
^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note:
candidate template ignored: couldn't infer template argument '_Fp'
function(_Fp,
^
1 error generated.
EDIT. В ответ на вопрос MSalters я сделал несколько поисков на этом форуме и нашел точную причину, почему это не удается. Я получил ответ от ответа Наваза в этом сообщении .
У меня есть копия, вставленная из его ответа здесь:
int test(const std::string&) {
return 0;
}
int test(const std::string*) {
return 0;
}
typedef int (*funtype)(const std::string&);
funtype fun = test; //no cast required now!
std::function<int(const std::string&)> func = fun; //no cast!
Итак, почему std::function<int(const std::string&)>
не работает так, как работает funtype fun = test
?
Ну, ответ заключается в том, что std::function
может быть инициализирован любым объектом, , поскольку его конструктор templatized, который не зависит от аргумента шаблона, который вы передали в std::function
.
Ответы
Ответ 1
Очевиден нам, какую функцию вы намерены выбрать, но компилятор должен следовать правилам С++, не использовать умные прыжки логики (или даже не такие умные, как в простых случаях, подобных этому!)
Соответствующим конструктором std::function
является:
template<class F> function(F f);
который является шаблоном, который принимает любой тип.
Стандарт С++ 14 ограничивает шаблон (поскольку LWG DR 2132), чтобы он:
не участвует в разрешении перегрузки, если f
не является вызываемым (20.9.12.2) для типов аргументов ArgTypes...
и возвращает тип R
.
что означает, что компилятор будет разрешать вызов конструктора только тогда, когда Functor
совместим с сигнатурой вызова std::function
(которая находится в void(int, int)
в вашем примере). Теоретически это должно означать, что void add(A, A)
не является жизнеспособным аргументом и поэтому "очевидно" вы намеревались использовать void add(int, int)
.
Однако компилятор не может протестировать ограничение "f
является вызываемым для аргументов типов..." до тех пор, пока не узнает тип f
, что означает, что он должен быть уже неоднозначным между void add(int, int)
и void add(A, A)
, прежде чем он сможет применить ограничение, которое позволит ему отклонить одну из этих функций!
Итак, есть ситуация с курицей и яйцом, что, к сожалению, означает, что вам нужно помочь компилятору, указав точно, какую перегрузку add
вы хотите использовать, а затем компилятор может применить ограничение и (довольно избыточно) решить что это приемлемый аргумент для конструктора.
Можно предположить, что мы могли бы изменить С++, чтобы в таких случаях все перегруженные функции тестировались против ограничения (поэтому нам не нужно знать, какой из них нужно протестировать перед его тестированием), и если только один из них жизнеспособен, тогда используйте это, но это не так, как работает С++.
Ответ 2
Хотя очевидно, что вы хотите, проблема в том, что std::function
не может влиять на разрешение перегрузки &add
. Если вы хотите инициализировать служебный указатель (void (*func)(int,int) = &add
), он работает. Это потому, что инициализация указателя функции - это контекст, в котором выполняется разрешение перегрузки. Тип цели точно известен. Но std::function
будет принимать практически любой аргумент, который можно вызывать. Эта гибкость при принятии аргументов означает, что вы не можете сделать разрешение перегрузки на &add
. Возможны несколько перегрузок add
.
Явное действие будет работать, т.е. static_cast<void(*)(int, int)> (&add)
.
Это можно обернуть в template<typename F> std::function<F> make_function(F*)
, который позволит вам написать auto func = make_function<int(int,int)> (&add)
Ответ 3
Try:
std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add);
Адреса в void add(A, A)
и void add(int, int)
obvoiusly различаются. Когда вы указываете на функцию по имени, для компилятора довольно сложно понять, какой адрес функции вам нужен. void(int, int)
здесь нет подсказки.
Ответ 4
Другой способ справиться с этим - с общей лямбдой в С++ 14:
int main() {
std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...);
}
Это создаст лямбда-функцию, которая будет разрешать вещи без какой-либо двусмысленности.
Я не пересылал аргументы,
Ответ 5
Насколько я вижу, это проблема Visual Studio.
С++ 11 standard (20.8.11)
std::function synopsis
template<class R, class... ArgTypes> class function<R(ArgTypes...)>;
но VisualStudio не имеет этой специализации
clang++ и g++ отлично справляются с перегрузкой std:: functions
предыдущие ответы объясняют, почему VS не работает, но они не упомянули, что это VS 'ошибка