I've got an function that iterates over a container and passes each element to a predicate for filtering. An overload of this function also passes the index of each element into the predicate.
template<typename TContainer>
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference)> predicate);
template<typename TContainer>
void DoSomethingIf(TContainer &c, std::function<bool (const typename TContainer::const_reference, int)> predicate);
I have found that attempting to call either of these functions with a naked lambda will cause a compiler error in VC11, while using a std::function object will succeed:
void foo()
{
std::vector<int> v;
// fails
DoSomethingIf(v, [](const int &x) { return x == 0; });
// also fails
auto lambda = [](const int &x) { return x == 0; };
DoSomethingIf(v, lambda);
// success!
std::function<bool (const int &)> fn = [](const int &x) { return x == 0; };
DoSomethingIf(v, fn);
}
1>c:\users\moswald\test.cpp(15): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function
1> c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &,int)
1> ]
1> c:\users\moswald\test.cpp(5): or 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &)
1> ]
1> while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3C>)'
1> with
1> [
1> _Ty=int
1> ]
1>c:\users\moswald\test.cpp(19): error C2668: 'DoSomethingIf' : ambiguous call to overloaded function
1> c:\users\moswald\test.cpp(8): could be 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &,int)
1> ]
1> c:\users\moswald\test.cpp(5): or 'void DoSomethingIf<std::vector<_Ty>>(TContainer &,std::function<_Fty>)'
1> with
1> [
1> _Ty=int,
1> TContainer=std::vector<int>,
1> _Fty=bool (const int &)
1> ]
1> while trying to match the argument list '(std::vector<_Ty>, foo::<lambda_8EADDE04A8D35A3D>)'
1> with
1> [
1> _Ty=int
1> ]
Is this to be expected? Is there a different way to overload these functions (short of renaming one to be "DoSomethingIfWithIndex
"?
No, you can not overload the lambda! Which is not possible, as the same variable name can not be reused in C++.
Restrictions on overloadingAny two functions in a set of overloaded functions must have different argument lists. Overloading functions that have argument lists of the same types, based on return type alone, is an error.
The overload ambiguity is expected.
std::function
has a converting constructor template that accepts any argument. Only after the constructor template is instantiated can the compiler determine that it will reject the argument.
In both your first and second examples, a user-defined conversion is required to convert the unspecified lambda type to each of the std::function
types. Neither conversion is better (they are both user-defined conversions), so the compiler reports the overload ambiguity.
In your third example (the one that works), there is no ambiguity because the std::function
constructor template is not used. Instead, its copy constructor is used (and, all other things being equal, nontemplates are preferred over templates).
std::function
has its uses at binary delimitations, but not as a general-use parameter for functors. As you've just discovered, its converting constructor interacts badly with overload resolution (and that has nothing to do with lambda expressions). Since DoSomethingIf
is already a template, I don't see a problem with the canonical solution of accepting generalized functors:
template<typename TContainer, typename Predicate>
void DoSomethingIf(TContainer& c, Predicate&& predicate);
As you may notice though, this version can't be overloaded and simply will accept anything as a predicate, even int
. Overloading is easily solved with SFINAE, as usual:
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference)>::value
>::type
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// dummy parameter to disambiguate this definition from the previous one
, typename = void
>
void
DoSomethingIf(Container& container, Predicate&& predicate);
This still has the annoying issue that if someone passes a predicate (or really anything) that doesn't satisfy our conditions, we get a 'no matching function found' error (an overload resolution failure) rather than a helpful error. If you want to solve that, you can add a 'catch-all' overload:
template<
typename Container
, typename Predicate
, typename = typename std::enable_if<
!is_callable<Predicate, bool(typename Container::const_reference)>::value
&& !is_callable<Predicate, bool(typename Container::const_reference, int)>::value
>::type
// more dummies
, typename = void, typename = void
>
void DoSomethingIf(Container&, Predicate&&)
{ static_assert( dependent_false_type<Container>::value,
"Put useful error message here" ); }
(dependent_false_type
just has to be e.g. a type inheriting from std::false_type
, we can't static_assert
on simply false
or that will be triggered every time, not just when the template is instantiated, as we'd like. Alternatively you can repeat the condition we have inside std::enable_if
here, which works somewhat as documentation inside the code, but doesn't improve the functionality itself.)
All that remains is where to find is_callable<Functor, Signature>
, as it's not in fact a Standard trait. It's relatively easy to implement if you've ever written an SFINAE test before, but a bit tedious as you have to partially specialize for void
returns. I'm not putting a specialization here as this answer is long enough as it is.
If you find this solution powerful but too verbose, then perhaps you'd enjoy concepts :)
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