Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do C++ lambdas not correctly select overloaded functions?

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"?

like image 670
moswald Avatar asked Feb 18 '12 08:02

moswald


People also ask

Can lambda functions be overloaded?

No, you can not overload the lambda! Which is not possible, as the same variable name can not be reused in C++.

What are the restrictions on overloaded functions?

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.


2 Answers

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).

like image 75
James McNellis Avatar answered Sep 27 '22 02:09

James McNellis


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 :)

like image 35
Luc Danton Avatar answered Sep 23 '22 02:09

Luc Danton