11

Is it possible to define a lambda in C++ with default generic argument?

int main(){
    auto lambda = [](auto i = 0){return i;};
    std::cout<<lambda()<<"\n"; // this does not compile
    std::cout<<lambda(4)<<"\n"; // this does compile
    auto lambda2 = [](int i = 0){return i;};
    std::cout<<lambda2()<<"\n"; // this is also OK
}

I wonder if it's possible to reproduce something like this functor, and why not

struct Lambda{
    template<typename T=int>
    auto operator()(T i=1){ return i;}
};
5
  • 3
    Issue with lambda is that it would produce something like struct Lambda{ template<typename T> auto operator()(T i=1) const{ return i;} }; which cannot be use either as Lambda{}(). Commented Jun 8, 2018 at 14:11
  • it will be possible with template lambda in C++20 Commented Jun 8, 2018 at 14:11
  • @Jarod42 But why deduction doesn't work in your example? Commented Jun 8, 2018 at 14:12
  • 4
    related/dupe: stackoverflow.com/questions/30240131/… Commented Jun 8, 2018 at 14:13
  • 3
    @HolyBlackCat: "The non-deduced contexts are: [...] — A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.". Commented Jun 8, 2018 at 14:16

1 Answer 1

2

You can do that with a wrapper:

template<class F, class DefaultArg>
struct DefaultArgWrapper
{
    F f;
    DefaultArg default_arg;

    template<class... Args>
    decltype(auto) operator()(Args&&... args) {
        return f(std::forward<Args>(args)...);
    }

    decltype(auto) operator()() {
        return f(default_arg);
    }
};

template<class F, class DefaultArg>
DefaultArgWrapper<F, DefaultArg> with_default_arg(F&& f, DefaultArg arg) {
    return {std::move(f), std::move(arg)};
}

int main(){
    auto lambda = with_default_arg([](auto i){return i;}, 0);
    std::cout<<lambda()<<"\n"; 
    std::cout<<lambda(4)<<"\n";
}

An alternative C++17 solution:

template<class... F>
struct ComposeF : F... {
    template<class... F2>
    ComposeF(F2&&... fs)
        : F(std::forward<F2>(fs))...
    {}
    using F::operator()...;
};

template<class... F>
ComposeF<std::decay_t<F>...> composef(F&&... fs) {
    return {std::forward<F>(fs)...};
}

int main() {
    auto lambda = [](auto i) { return i; };
    auto f = composef(lambda, [&lambda](int i = 0) { return lambda(i); });
    std::cout << f() << '\n';
    std::cout << f(1) << '\n';
}

A bit sub-optimal though, since there are two copies of lambda involved: one copy in ComposeF, another is the original lambda on the stack. If lambda is mutable that would be an issue.

Sign up to request clarification or add additional context in comments.

2 Comments

… with the caveat, that, unlike a "true" default argument, the expression with which arg is initialized, is only evaluated once, at the creation of the functor. This can have some possibly unexpected lifetime interactions as well, e.g. when DefaultArg is an instance of std::initializer_list as in bool my_is_sorted(const std::vector<int> &v);with_default_arg(my_is_sorted, {1,2,3}) // UB on call.
@ArneVogel Your counter-example does not compile because it cannot deduce DefaultArg from {1,2,3}. You are quite right though about the difference, it can matter somewhere, but I haven't seen production code where it would. Most often the default arguments are trivial types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.