在 C++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
is a NULL
函数指针。
-
f
is a NULL
指向成员的指针。
-
F
是函数类模板的实例,并且!f
9 否则,*this
目标副本f
初始化为std::move(f)
。 [此处留下注释]
10 Throws:当f
是一个函数指针或reference_wrapper<T>
对于一些T
。否则可能会抛出bad_alloc
或抛出的任何异常F
的复制或移动构造函数。
现在,构造或尝试构造(用于重载解析)std::function<void(int)>
from a [](){}
(即带有签名void(void)
)违反了要求std::function<void(int)>
的构造函数。
[必需响应]/1
违反函数中指定的前提条件要求:段落会导致未定义的行为,除非函数的Throws:段落指定在违反前提条件时抛出异常。
因此,据我所知,即使重载决策的结果也是未定义的。因此,g++/libstdc++ 的两个版本在这方面都是符合的。
在 C++14 中,这已更改,请参阅LWG 2132 http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2132。现在,转换构造函数模板std::function
需要 SFINAE 拒绝不兼容的 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 Remarks:这些构造函数不应参与重载
决议除非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
。然后,必须使用这些参数(参数类型)调用包装的函数对象。如果一个double
被传递到std::function
,它不能传递给采用std::string
,因为没有转换double
-> std::string
。
对于第一次过载foo
,论证[](std::string){}
因此不被视为可调用std::function<void(double)>
。构造函数模板已停用,因此没有可行的转换[](std::string){}
to std::function<void(double)>
。该第一重载被从用于解析调用(C)的重载集中移除,仅留下第二重载。
请注意,由于以下原因,上述措辞略有变化LWG 2420 http://cplusplus.github.io/LWG/lwg-defects.html#2420: 有一个例外,如果返回类型R
of a std::function<R(ArgTypes...)>
is void
,那么上面提到的构造函数模板中的 Callable 的任何返回类型都会被接受(并丢弃)。例如,两者[]() -> void {}
and []() -> 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.);
}