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++?
When a function call is made to an overloaded function, the compiler steps through a sequence of rules to determine which (if any) of the overloaded functions is the best match. At each step, the compiler applies a bunch of different type conversions to the argument(s) in the function call.
The process of selecting the most appropriate overloaded function or operator is called overload resolution. Suppose that f is an overloaded function name. When you call the overloaded function f() , the compiler creates a set of candidate functions.
You overload a function name f by declaring more than one function with the name f in the same scope. The declarations of f must differ from each other by the types and/or the number of arguments in the argument list.
There are two ways to resolve this ambiguity: Typecast char to float. Remove either one of the ambiguity generating functions float or double and add overloaded function with an int type parameter.
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 beCopyConstructible
.f
shall beCallable
(20.10.11.2) for argument typesArgTypes
and return typeR
. The copy constructor and destructor ofA
shall not throw exceptions.8 Postconditions:
!*this
if any of the following hold:
f
is aNULL
function pointer.f
is aNULL
pointer to member.F
is an instance of the function class template, and!f
9 Otherwise,
*this
targets a copy off
initialized withstd::move(f)
. [left out a note here]10 Throws: shall not throw exceptions when
f
is a function pointer or areference_wrapper<T>
for someT
. Otherwise, may throwbad_alloc
or any exception thrown byF
’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 beCopyConstructible
.8 Remarks: These constructors shall not participate in overload resolution unless
f
is Callable (20.9.11.2) for argument typesArgTypes...
and return typeR
.[...]
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.
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.);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With