Проблема в том, что и function<int()>
, и function<int(int)>
могут быть созданы из одной и той же функции. Вот как выглядит объявление конструктора std::function
в VS2010:
template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);
Игнорируя часть SFINAE, она может быть сделана практически из чего угодно.
std::/boost::function
использует метод, называемый стирание типа , чтобы разрешить передачу произвольных объектов / функций так долго, пока они удовлетворяют сигнатуре при вызове. Одним из недостатков этого является то, что вы получаете ошибку в самой глубокой части реализации (где вызывается сохраненная функция) при предоставлении объекта, который не может быть вызван так, как того требует сигнатура, а не в конструкторе.
Проблема может быть проиллюстрирована этим маленьким классом:
template<class Signature>
class myfunc{
public:
template<class Func>
myfunc(Func a_func){
// ...
}
};
Теперь, когда компилятор ищет допустимые функции для набора перегрузки, он пытается преобразовать аргументы, если не существует функции идеальной подгонки. Преобразование может происходить через конструктор параметра функции или через оператор преобразования аргумента, переданного функции. В нашем случае это первое.
Компилятор пробует первую перегрузку a
. Чтобы сделать его жизнеспособным, нужно сделать конверсию. Чтобы преобразовать int(*)()
в myfunc<int()>
, он пытается использовать конструктор myfunc
. Будучи шаблоном, который принимает все, преобразование, естественно, успешно.
Теперь он пытается то же самое со второй перегрузкой. Конструктор все тот же и принимает все, что ему дано, преобразование тоже работает.
Оставленный с двумя функциями в наборе перегрузки, компилятор - печальная панда и не знает, что делать, поэтому он просто говорит, что вызов неоднозначен.
Итак, в конце концов, часть шаблона Signature
принадлежит типу при создании объявлений / определений, но не когда вы хотите создать объект.
Редактировать
Сосредоточившись на ответе на заглавный вопрос, я совершенно забыл о вашем втором вопросе. (
Могу ли я обойти это или мне придется оставить (раздражающие) явные приведения?
Afaik, у вас есть 3 варианта.
- Оставить актерский состав
Создайте function
объект соответствующего типа и передайте его
function<int()> fx = x;
function<int(int)> fy = y;
a(fx);
a(fy);
Скрыть утомительный кастинг в функции и использовать TMP, чтобы получить правильную подпись
Версия TMP (шаблонное метапрограммирование) довольно многословна и содержит стандартный код, но скрывает приведение от клиента. Пример версии можно найти здесь , которая опирается на метафункцию get_signature
, которая частично специализируется на типах указателей функций (и предоставляет хороший пример того, как сопоставление с образцом может работать в C ++):
template<class F>
struct get_signature;
template<class R>
struct get_signature<R(*)()>{
typedef R type();
};
template<class R, class A1>
struct get_signature<R(*)(A1)>{
typedef R type(A1);
};
Конечно, это должно быть расширено для количества аргументов, которые вы хотите поддерживать, но это делается один раз, а затем скрывается в заголовке "get_signature.h"
. :)
Другим вариантом, который я рассмотрел, но сразу отбросил, был SFINAE, который ввел бы еще больше стандартного кода, чем версия TMP.
Итак, да, это варианты, которые я знаю. Надеюсь, что один из них работает для вас. :)