使用std :: function的过载解析 [英] Overload resolution with std::function

查看:185
本文介绍了使用std :: function的过载解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑此代码示例:

  #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!\\\
;});
}



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



这个代码在C ++ 11中是否正确,或者是这个版本的g ++的错误/ p>

解决方案

在C ++ 11 ...



std :: function (它接受任何Callable)的构造函数模板的规范:[func.wrap.func.con] / 7-10


  template< class F>函数(F f); 
template< class F,class A>函数(allocator_arg_t,const A& a,F f);

7 需要: F 应为 CopyConstructible f 应为可调用(20.10.11.2)的参数类型 ArgTypes 并返回类型
R A 的复制构造函数和析构函数不得引发
异常。



8 后置条件:


  • code> f 是 NULL 函数指针。

  • f 是指向成员的 NULL 指针。

  • F 是函数类模板的一个实例,!f



< 9否则, * this 定位用 std :: move(f)初始化的 f

f 是 T 的函数指针或 reference_wrapper< T> 否则,可能抛出
bad_alloc 或者由 F 的复制或移动构造函数抛出的任何异常。 p>

现在,构造或试图构建一个 std :: function< void(int) < code> void(void) [](){} >)违反 std :: function< void(int)> 的构造函数的要求。



res.on.required] / 1


违反函数中要求的前提条件:未定义的行为,除非函数的 Throws:段落指定在违反前提条件时抛出异常。


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






在C ++ 14中, ,请参阅 LWG 2132 。现在, std :: function 的转换构造函数模板是SFINAE拒绝不兼容的Callables所必需的(更多关于下一章的SFINAE):


 模板< class F>函数(F f); 
template< class F,class A>函数(allocator_arg_t,const A& a,F f);

7 需要: F 应为 CopyConstructible



8 备注:在过载
解析,除非 f 是Callable(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 ,它不能被传递到一个函数, $ c> std :: string ,因为没有转换 double - > std :: string
对于 foo 的第一个重载, [](std :: string){} 因此不被认为是 std :: function< void(double)> 的Callable。构造函数模板被停用,因此没有可行的转换从 [](std :: string){} std :: function< void )> 。这个第一个过载被从用于解决调用的过载集合(C)中去除,只留下第二个过载。



请注意,由于 LWG 2420 :有一个例外,如果返回类型<$ std :: function< R(ArgTypes ...)> R / code>,那么在上述构造函数模板中,任何返回类型都被接受(并丢弃)为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 \\\
; }

template< class T>
auto foo(T) - > typename std :: enable_if< not std :: is_integral< T> :: value> :: type
{std :: cout< foo 2 \\\
; }

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)\\\
; }
};

struct bar
{
template< class T,class =
typename std :: enable_if< not std :: is_integral< T> :: value> :: type>
bar(T)
{std :: cout< bar(T)\\\
; }
};

struct kitty
{
kitty(foo){}
kitty(bar){}
};

int main()
{
kitty cat(42);
kitty tac(42。);
}


Consider this example of code:

#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"; });
}

I was sure that it shouldn't compile, because the compiler shouldn't be able to choose one of the two constructors. g++-4.7.3 shows this expected behavior: it says that call of overloaded constructor is ambiguous. However, g++-4.8.2 successfully compiles it.

Is this code correct in C++11 or it is a bug/feature of this version of g++?

解决方案

In C++11...

Let's take a look at the specification of the constructor template of std::function (which takes any 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 Requires: F shall be CopyConstructible. f shall be Callable (20.10.11.2) for argument types ArgTypes and return type R. The copy constructor and destructor of A shall not throw exceptions.

8 Postconditions: !*this if any of the following hold:

  • f is a NULL function pointer.
  • f is a NULL pointer to member.
  • F is an instance of the function class template, and !f

9 Otherwise, *this targets a copy of f initialized with std::move(f). [left out a note here]

10 Throws: shall not throw exceptions when f is a function pointer or a reference_wrapper<T> for some T. Otherwise, may throw bad_alloc or any exception thrown by F’s copy or move constructor.

Now, constructing, or attempting to construct (for overload resolution) a std::function<void(int)> from a [](){} (i.e. with signature void(void)) violates the requirements of std::function<void(int)>'s constructor.

[res.on.required]/1

Violation of the preconditions specified in a function’s Requires: paragraph results in undefined behavior unless the function’s Throws: paragraph specifies throwing an exception when the precondition is violated.

So, AFAIK, even the result of the overload resolution is undefined. Therefore, both versions of g++/libstdc++ are complying in this aspect.


In C++14, this has been changed, see LWG 2132. Now, the converting constructor template of std::function is required to SFINAE-reject incompatible Callables (more about SFINAE in the next chapter):

template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

7 Requires: F shall be CopyConstructible.

8 Remarks: These constructors shall not participate in overload resolution unless f is Callable (20.9.11.2) for argument types ArgTypes... and return type R.

[...]

The "shall not participate in overload resolution" corresponds to rejection via SFINAE. The net effect is that if you have an overload set of functions foo,

void foo(std::function<void(double)>);
void foo(std::function<void(char const*)>);

and a call-expression such as

foo([](std::string){}) // (C)

then the second overload of foo is chosen unambiguously: Since std::function<F> defines F as its interface to the outside, the F defines which argument types are passed into std::function. Then, the wrapped function object has to be called with those arguments (argument types). If a double is passed into std::function, it cannot be passed on to a function taking a std::string, because there's no conversion double -> std::string. For the first overload of foo, the argument [](std::string){} is therefore not considered Callable for std::function<void(double)>. The constructor template is deactivated, hence there's no viable conversion from [](std::string){} to std::function<void(double)>. This first overload is removed from the overload set for resolving the call (C), leaving only the second overload.

Note that there's been a slight change to the wording above, due to LWG 2420: There's an exception that if the return type R of a std::function<R(ArgTypes...)> is void, then any return type is accepted (and discarded) for the Callable in the constructor template mentioned above. For example, both []() -> void {} and []() -> bool {} are Callable for std::function<void()>. The following situation therefore produces an ambiguity:

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

foo([]() -> bool {}); // ambiguous

The overload resolution rules don't try to rank among different user-defined conversions, and hence both overloads of foo are viable (first of all) and neither is better.


How can SFINAE help here?

Note when a SFINAE-check fails, the program isn't ill-formed, but the function isn't viable for overload resolution. For example:

#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.);
}

Similarly, a conversion can be made non-viable by using SFINAE on the converting constructor:

#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.);
}

这篇关于使用std :: function的过载解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆