使用std :: function重载解析

lis*_*rus 15 c++ lambda overloading c++11

考虑这个代码示例:

#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"; });
}
Run Code Online (Sandbox Code Playgroud)

我确信它不应该编译,因为编译器不应该能够选择两个构造函数中的一个.g ++ - 4.7.3显示了这种预期的行为:它表示重载构造函数的调用是不明确的.但是,g ++ - 4.8.2成功编译了它.

这个代码在C++ 11中是正确的还是这个版本的g ++的bug /功能?

dyp*_*dyp 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);
Run Code Online (Sandbox Code Playgroud)

7 要求: F应该是CopyConstructible.对于参数类型和返回类型, f应为Callable(20.10.11.2).复制构造函数和析构函数不应抛出异常.ArgTypesRA

8 后置条件: !*this如果满足以下任何条件:

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

9否则,将*this目标作为f初始化的副本std::move(f).[在这里留下一个注释]

10 抛出:f函数指针或reference_wrapper<T>某些函数指针时,不应抛出异常T.否则,可能会抛出 bad_allocF复制或移动构造函数抛出的任何异常.

现在,构造或尝试构造(用于重载解析)a(std::function<void(int)>[](){}签名void(void))违反了std::function<void(int)>构造函数的要求.

[res.on.required]/1

违反函数的Requires:段中指定的前提条件会导致未定义的行为,除非函数的Throws: paragraph指定在违反前提条件时抛出异常.

所以,AFAIK,即使是重载分辨率的结果也是未定义的.因此,g ++/libstdc ++的两个版本都符合这一方面.


在C++ 14中,这已经改变,参见LWG 2132.现在,std::functionSFINAE拒绝不兼容的Callables需要转换构造函数模板(下一章更多关于SFINAE):

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
Run Code Online (Sandbox Code Playgroud)

7 要求: F应该是CopyConstructible.

8 备注:除非fCallable(20.9.11.2)用于参数类型ArgTypes...和返回类型,否则这些构造函数不应参与重载决策 R.

[...]

"不应参与超载分辨率"对应于SFINAE的拒绝.实际效果是,如果你有一组重载函数foo,

void foo(std::function<void(double)>);
void foo(std::function<void(char const*)>);
Run Code Online (Sandbox Code Playgroud)

和一个如表达式的调用表达式

foo([](std::string){}) // (C)
Run Code Online (Sandbox Code Playgroud)

然后foo明确地选择第二个重载:由于std::function<F>定义F为它与外部的接口,F定义传递哪个参数类型std::function.然后,必须使用这些参数(参数类型)调用包装的函数对象.如果double传入a std::function,则无法将其传递给采用a的函数std::string,因为没有转换double- > std::string.因此,对于第一个重载foo,参数[](std::string){}不被视为可调用std::function<void(double)>.构造函数模板被停用,因此有一个从没有可行的转换[](std::string){}std::function<void(double)>.第一个重载从过载集中删除,用于解析调用(C),只留下第二个重载.

请注意,一直到上面的措辞略有变化,由于LWG 2420:有,如果返回类型的例外Rstd::function<R(ArgTypes...)>void,那么任何返回类型被接受(和丢弃)在上述构造模板调用.例如,无论是[]() -> void {}[]() -> bool {}是可赎回的std::function<void()>.因此,以下情况会产生歧义:

void foo(std::function<void()>);
void foo(std::function<bool()>);

foo([]() -> bool {}); // ambiguous
Run Code Online (Sandbox Code Playgroud)

重载决策规则不会尝试在不同的用户定义的转换之间进行排名,因此两个重载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.);
}
Run Code Online (Sandbox Code Playgroud)

类似地,通过在转换构造函数上使用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.);
}
Run Code Online (Sandbox Code Playgroud)

  • (记录中,反对投票的不是我):我只是想指出这个[DR](http://www.open-std.org/jtc1/sc22/wg21/docs/lwg -active.html#2132)gcc已经在实施. (2认同)