Разве шаблонный аргумент (подпись) std :: function не является частью его типа? - PullRequest
39 голосов
/ 09 мая 2011

Учитывая следующий код, что является причиной двусмысленности?Могу ли я обойти это или мне придется сохранять (раздражающие) явные приведения?

#include <functional>

using namespace std;

int a(const function<int ()>& f)
{
    return f();
}

int a(const function<int (int)>& f)
{
    return f(0);
}

int x() { return 22; }

int y(int) { return 44; }

int main()
{
    a(x);  // Call is ambiguous.
    a(y);  // Call is ambiguous.

    a((function<int ()>)x);    // Works.
    a((function<int (int)>)y); // Works.

    return 0;
}

Интересно, если я закомментирую функцию a() с параметром function<int ()> и вызову a(x) в моемmain, компиляция корректно завершается неудачей из-за несоответствия типов между x и аргументом function<int (int)> единственной доступной функции a().Если компилятор в этом случае завершится неудачно, почему возникнет двусмысленность при наличии двух a() функций?

Я пробовал с VS2010 и g ++ v. 4.5.Оба дают мне одинаковую двусмысленность.

Ответы [ 4 ]

40 голосов
/ 09 мая 2011

Проблема в том, что и 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.

Итак, да, это варианты, которые я знаю. Надеюсь, что один из них работает для вас. :)

11 голосов
/ 01 июня 2011

Я видел, как этот вопрос поднимался слишком много раз. libc ++ теперь компилирует этот код без неоднозначности (как соответствующее расширение).

Просроченное обновление

Это «расширение» оказалось достаточно популярным, так как оно было стандартизировано в C ++ 14 (хотя я лично не отвечал за выполнение этой работы).

Оглядываясь назад, я не совсем правильно понял это расширение. Ранее в этом месяце (2015-05-09) комитет проголосовал за выпуск LWG 2420 , который эффективно меняет определение Callable , так что если std::function имеет тип возврата void он будет игнорировать возвращаемый тип обернутого функтора, но все равно будет считать его вызываемым , если все остальное совпадает, вместо того, чтобы считать его не вызываемым .

Этот твик после C ++ 14 не влияет на этот конкретный пример, поскольку используемые типы возвращаемых значений последовательно int.

4 голосов
/ 21 августа 2012

Вот пример того, как обернуть std::function в классе, который проверяет неповторимость его параметров конструктора:

template<typename> struct check_function;
template<typename R, typename... Args>
struct check_function<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_same<R, void>::value
            || std::is_convertible<
                decltype(std::declval<T>()(std::declval<Args>()...)),
                R>::value>::type>
        check_function(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};

Использовать так:

int a(check_function<int ()> f) { return f(); }
int a(check_function<int (int)> f) { return f(0); }

int x() { return 22; }
int y(int) { return 44; }

int main() {
    a(x);
    a(y);
}

Обратите внимание, что это неэто не то же самое, что перегрузка сигнатуры функции, поскольку она рассматривает конвертируемые аргументы (и возвращаемые) как эквивалентные.Для точной перегрузки это должно работать:

template<typename> struct check_function_exact;
template<typename R, typename... Args>
struct check_function_exact<R(Args...)>: public std::function<R(Args...)> {
    template<typename T,
        class = typename std::enable_if<
            std::is_convertible<T, R(*)(Args...)>::value>::type>
        check_function_exact(T &&t): std::function<R(Args...)>(std::forward<T>(t)) { }
};
2 голосов
/ 09 мая 2011

std::function<T> имеет ctor преобразования, который принимает произвольный тип (то есть что-то отличное от T). Конечно, в этом случае этот ctor приведет к ошибке несоответствия типов, но компилятор не заходит так далеко - вызов неоднозначен просто потому, что ctor существует.

...