使用std :: function重载解析

时间:2014-03-03 12:25:04

标签: c++ c++11 lambda overloading

考虑这个代码示例:

#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 ++的错误/特性?

2 个答案:

答案 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)   RA的拷贝构造函数和析构函数不会抛出   异常。

     

8 后置条件: !*this如果符合以下任何条件:

     
      
  • fNULL函数指针。
  •   
  • f是指向成员的NULL指针。
  •   
  • F是函数类模板的一个实例,!f
  •   
     

9否则,*this会定位使用f初始化的std::move(f)副本。 [在这里留下一个注释]

     

10 抛出:f是函数指针或某些reference_wrapper<T>的{​​{1}}时不会抛出异常。否则,可能会抛出   Tbad_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接受(并丢弃)任何返回类型。例如,Rstd::function<R(ArgTypes...)>都可以void调用。因此,以下情况会产生歧义:

[]() -> void {}

重载决策规则不会尝试在不同的用户定义的转换中进行排名,因此[]() -> bool {}的两个重载都是可行的(首先)并且两者都不是更好。


SFINAE如何在这里提供帮助?

注意当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);

source

编辑:如果在提供的示例中替换

,可能会更清楚
F1 -> std::function<void()> 
F2 -> std::function<void(int)>

see it compile in gcc4.7

模板化解决方案仅用于证明概念可以扩展到通用代码并且可以进行消歧。

在您的情况下,当使用较旧的编译器(如gcc 4.7 )时,您可以通过显式转换和gcc will work things out, as you can see in this live example

来提供帮助

以防万一你想知道,如果你转向另一种方式(尝试将lambda转换为带有无参数的std :: function等等),它将无效。