Различные перегрузки с параметрами std::function неоднозначны с привязкой (иногда)

У меня есть две перегрузки функции foo, которые принимают разные std::function, что приводит к проблеме неоднозначности для последнего при использовании с результатом std::bind. Я не понимаю, почему только это двусмысленно.

void foo(std::function<void(int)>) {}
void foo(std::function<int()>) {}

void take_int(int) { }
int ret_int() { return 0; }

При использовании int() с функцией bind я получаю ошибку неоднозначности

foo(std::bind(ret_int)); // ERROR

С ошибкой gcc-5.1 (и аналогичной с clang)

error: call to 'foo' is ambiguous
  foo(std::bind(ret_int));
  ^~~
note: candidate function
void foo(std::function<void(int)>) {}
     ^
note: candidate function
void foo(std::function<int()>) {}

Однако все последующие работы

foo(std::bind(take_int, _1));
foo(take_int);

foo(ret_int);
foo([](){ return ret_int(); });

struct TakeInt {
  void operator()(int) const { }
};

struct RetInt {
  int operator()() const { return 0; }
};

foo(TakeInt{});
foo(RetInt{});

Глядя на конструктор std::function

template< class F > 
function( F f );

для меня было бы разумным, чтобы любая функция с несколькими перегрузками для разных типов std::function имела двусмысленность, но это только проблема с вызовом для привязки. Затем я подумал: «Может быть, при обработке типов функций и лямбда-выражений происходит какое-то волшебство, и оно не имеет отношения к реальным классам», но оно также обрабатывает и их.

В en.cppreference есть примечание, в котором говорится [начиная с С++ 14]

Этот конструктор не участвует в разрешении перегрузки, если f не является Callable для типов аргументов Args... и возвращает тип R.


person Ryan Haining    schedule 01.07.2015    source источник
comment
Связанный: stackoverflow.com/questions/30227097/ и stackoverflow.com/questions/22146749/   -  person 0x499602D2    schedule 02.07.2015


Ответы (1)


Проблема заключается в том, как разрешен вызов bind. Как состояния cppreference

Если некоторые из аргументов, предоставленных при вызове g(), не совпадают ни с одним заполнителем, хранящимся в g, неиспользуемые аргументы оцениваются и отбрасываются.

Другими словами, вам нужно передать как минимум столько аргументов, сколько ожидает базовый вызываемый объект.

Это означает, что справедливо следующее

int f();
auto b = std::bind(f);
b(1, 2, 3); // arguments aren't used

Так сказать

auto b = std::bind(ret_int)
b(1);

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

std::function<void(int)> f = std::bind(ret_int);

Однако обратное неверно

std::function<int()> f = std::bind(take_int);

потому что take_int нельзя вызвать без аргументов.

Вывод: лямбда > привязать

person Ryan Haining    schedule 02.07.2015
comment
Где в стандарте сказано, что лишние аргументы отбрасываются? Я не могу найти его. - person 0x499602D2; 02.07.2015
comment
@ 0x499602D2 Не знаю, я остановился на cppreference на этом. я тоже сейчас смотрю - person Ryan Haining; 02.07.2015
comment
Я думаю, что нашел его, хотя это может показаться немного надуманным: N4431 20.9.2/4. A forwarding call wrapper is a call wrapper that can be called with an arbitrary argument list and delivers the arguments to the wrapped callable object as references. В заметке даже используются вариады. - person user703016; 02.07.2015