Ответ 1
В С++ 11...
Посмотрим на спецификацию шаблона конструктора std::function
(который принимает любую Callable): [func.wrap.func.con]/7-10
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 Требуется:
F
должно бытьCopyConstructible
.F
должен бытьCallable
(20.10.11.2) для типов аргументовArgTypes
и типа возвратаR
. Конструктор копирования и деструкторA
не должны бросать исключения.8 Постусловия:
!*this
, если выполнено одно из следующих действий:
F
является указателем функцииNULL
.F
является указателемNULL
для члена.F
- это экземпляр шаблона класса функций, а!f
9 В противном случае
*this
задает копиюF
, инициализированную с помощьюstd::move(f)
. [оставить здесь примечание]10 Броски: не должны бросать исключения, если
F
является указателем на функцию илиreference_wrapper<T>
для некоторогоT
. В противном случае, можетbad_alloc
или любое исключение, созданноеF
s копированием или перемещением конструктора.
Теперь, создавая или пытаясь построить (для разрешения перегрузки) a std::function<void(int)>
из [](){}
(т.е. с сигнатурой void(void)
) нарушает требования конструктора std::function<void(int)>
.
[res.on.required]/1
Нарушение предусловий, указанных в функциях. Требуется: абзац приводит к поведению undefined, если только функции Throws: paragraph указывает на исключение исключения при условии, что предварительное условие нарушено.
Итак, AFAIK, даже результат разрешения перегрузки undefined. Поэтому в этом аспекте выполняются обе версии g++/libstdС++.
В С++ 14 это было изменено, см. LWG 2132. Теперь для SFINAE-шаблона конструктора преобразования std::function
требуется отказ от несовместимых Callables (подробнее о SFINAE в следующей главе):
template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
7 Требуется:
F
должно бытьCopyConstructible
.8 Примечания: Эти конструкторы не должны участвовать в перегрузке если
F
является вызываемым (20.9.11.2) для типов аргументовArgTypes...
и тип возвратаR
.[...]
"Не участвует в разрешении перегрузки" соответствует отказу через SFINAE. Чистый эффект заключается в том, что если у вас есть набор функций перегрузки foo
,
void foo(std::function<void(double)>);
void foo(std::function<void(char const*)>);
и выражение вызова, такое как
foo([](std::string){}) // (C)
то вторая перегрузка foo
выбирается однозначно: поскольку std::function<F>
определяет F
как свой внешний интерфейс, F
определяет, какие типы аргументов передаются в std::function
. Затем объект завершенной функции должен вызываться с этими аргументами (типами аргументов). Если a double
передается в std::function
, он не может быть передан функции, принимающей std::string
, потому что нет преобразования double
→ std::string
.
Для первой перегрузки foo
аргумент [](std::string){}
поэтому не считается Callable для std::function<void(double)>
. Шаблон конструктора деактивируется, поэтому нет жизнеспособного преобразования от [](std::string){}
до std::function<void(double)>
. Эта первая перегрузка удаляется из набора перегрузки для разрешения вызова (C), оставляя только вторую перегрузку.
Обратите внимание на небольшое изменение в формулировке выше, из-за LWG 2420: Исключено, что если тип возврата R
из std::function<R(ArgTypes...)>
является void
, тогда любой возвращаемый тип принимается (и отбрасывается) для Callable в шаблоне конструктора, упомянутом выше. Например, как []() -> void {}
, так и []() -> bool {}
являются вызываемыми для std::function<void()>
. Поэтому следующая ситуация создает двусмысленность:
void foo(std::function<void()>);
void foo(std::function<bool()>);
foo([]() -> bool {}); // ambiguous
Правила разрешения перегрузки не пытаются ранжироваться среди разных пользовательских преобразований, и поэтому обе перегрузки foo
жизнеспособны (прежде всего), и ни одна из них не лучше.
Как может помочь SFINAE?
Обратите внимание, что при сбое проверки SFINAE программа не плохо сформирована, но эта функция не является жизнеспособной для разрешения перегрузки. Например:
#include <type_traits>
#include <iostream>
template<class T>
auto foo(T) -> typename std::enable_if< std::is_integral<T>::value >::type
{ std::cout << "foo 1\n"; }
template<class T>
auto foo(T) -> typename std::enable_if< not std::is_integral<T>::value >::type
{ std::cout << "foo 2\n"; }
int main()
{
foo(42);
foo(42.);
}
Аналогично, преобразование может быть сделано нежизнеспособным, используя SFINAE в конструкторе преобразования:
#include <type_traits>
#include <iostream>
struct foo
{
template<class T, class =
typename std::enable_if< std::is_integral<T>::value >::type >
foo(T)
{ std::cout << "foo(T)\n"; }
};
struct bar
{
template<class T, class =
typename std::enable_if< not std::is_integral<T>::value >::type >
bar(T)
{ std::cout << "bar(T)\n"; }
};
struct kitty
{
kitty(foo) {}
kitty(bar) {}
};
int main()
{
kitty cat(42);
kitty tac(42.);
}