考虑这个代码示例:
#include <iostream>
#include <functional>
typedef std::function<void()> func1_t;
typedef std::function<void(int)> func2_t;
struct X
{
X (func1_t f)
{ }
X (func2_t f)
{ }
};
int main ( )
{
X x([](){ std::cout << "Hello, world!\n"; });
}
我确信它不应该编译,因为编译器不应该能够选择两个构造函数中的一个。 g ++ - 4.7.3显示了这种预期的行为:它表示重载构造函数的调用是不明确的。但是,g ++ - 4.8.2成功编译了它。
这个代码在C ++ 11中是否正确,或者是这个版本的g ++的错误/特性?
答案 0 :(得分:11)
在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
应为ArgTypes
(20.10.11.2)R
。A
的拷贝构造函数和析构函数不会抛出 异常。8 后置条件:
!*this
如果符合以下任何条件:
f
是NULL
函数指针。f
是指向成员的NULL
指针。F
是函数类模板的一个实例,!f
9否则,
*this
会定位使用f
初始化的std::move(f)
副本。 [在这里留下一个注释]10 抛出:在
f
是函数指针或某些reference_wrapper<T>
的{{1}}时不会抛出异常。否则,可能会抛出T
或bad_alloc
的复制或移动构造函数抛出的任何异常。
现在,构建或尝试构建(用于重载解析)来自F
的{{1}}(即带有签名std::function<void(int)>
)违反了[](){}
的要求构造
[res.on.required] / 1
违反函数的 Requires:段中指定的前提条件会导致未定义的行为,除非函数的抛出:段落指定在违反前提条件时抛出异常。
所以,AFAIK,即使重载解析的结果也是未定义的。因此,g ++ / libstdc ++的两个版本都符合这一方面。
在C ++ 14中,这已经改变,请参阅LWG 2132。现在,SFINAE拒绝不兼容的Callables需要void(void)
的转换构造函数模板(下一章更多关于SFINAE):
std::function<void(int)>
7 要求:
std::function
应为template<class F> function(F f); template <class F, class A> function(allocator_arg_t, const A& a, F f);
。8 备注:这些构造函数不应参与重载 解析除非
F
是可调用的(20.9.11.2)参数类型CopyConstructible
并返回f
类型。[...]
“不参与重载决议”对应于通过SFINAE拒绝。实际效果是,如果您有一组重载函数ArgTypes...
,
R
和一个调用表达式,如
foo
然后明确选择void foo(std::function<void(double)>);
void foo(std::function<void(char const*)>);
的第二个重载:由于foo([](std::string){}) // (C)
将foo
定义为外部接口,std::function<F>
定义传入哪些参数类型{ {1}}。然后,必须使用这些参数(参数类型)调用包装的函数对象。如果将F
传递到F
,则无法将其传递给使用std::function
的函数,因为没有转化double
- &gt; std::function
。
对于std::string
的第一次重载,参数double
因此不被视为std::string
的可调用。构造函数模板已停用,因此无法从foo
转换为[](std::string){}
。第一个重载从过载集中删除,用于解析调用(C),只留下第二个重载。
请注意,由于LWG 2420,上述措辞略有变化:如果std::function<void(double)>
的返回类型[](std::string){}
为std::function<void(double)>
,则会出现例外情况,然后在上面提到的构造函数模板中为Callable接受(并丢弃)任何返回类型。例如,R
和std::function<R(ArgTypes...)>
都可以void
调用。因此,以下情况会产生歧义:
[]() -> void {}
重载决策规则不会尝试在不同的用户定义的转换中进行排名,因此[]() -> bool {}
的两个重载都是可行的(首先)并且两者都不是更好。
注意当SFINAE检查失败时,程序没有格式错误,但该功能不适用于重载分辨率。例如:
std::function<void()>
类似地,通过在转换构造函数上使用SFINAE,可以使转换不可行:
void foo(std::function<void()>);
void foo(std::function<bool()>);
foo([]() -> bool {}); // ambiguous
答案 1 :(得分:-3)
完全有效。由于c ++ 11 lambda表达式(和你的std::function
包装器)创建了函数对象。功能对象的强大之处在于,即使它们是通用的,它们仍然是一流的对象。与普通函数模板不同,它们可以传递给函数并从函数返回。
您可以使用继承和使用声明显式创建运算符重载集。 Mathias Gaunard中的以下用法演示了“重载的lambda表达式”。
template <class F1, class F2>
struct overload_set : F1, F2
{
overload_set(F1 x1, F2 x2) : F1(x1), F2(x2) {}
using F1::operator();
using F2::operator();
};
template <class F1, class F2>
overload_set<F1,F2> overload(F1 x1, F2 x2)
{
return overload_set<F1,F2>(x1,x2);
}
auto f = overload(
[](){return 1;},
[](int x){return x+1;}
);
int x = f();
int y = f(2);
编辑:如果在提供的示例中替换
,可能会更清楚F1 -> std::function<void()>
F2 -> std::function<void(int)>
模板化解决方案仅用于证明概念可以扩展到通用代码并且可以进行消歧。
在您的情况下,当使用较旧的编译器(如gcc 4.7 )时,您可以通过显式转换和gcc will work things out, as you can see in this live example
来提供帮助以防万一你想知道,如果你转向另一种方式(尝试将lambda转换为带有无参数的std :: function等等),它将无效。