Go crazy and use variadic templates everywhere, use lambdas everywhere, use
std::bind() everywhere, ... Each has its place; each could be more appropriate in a particular context. As always, one size does not fit all.
Typically, a flxible design is not an either this or that design - variadic templates and callable objects (closures/lambdas, functors, call-wrappers, functions curried by std::bind) all co-exist very well. This allows the most appropriate choice to be made seamlessly on a per local-context basis. A typical example of a flexible design approach is
std::threrad
1 2 3 4 5 6 7 8
|
class thread
{
public:
// ...
template < typename CALLABLE_OBJECT, typename... ARGS >
explicit thread( CALLABLE_OBJECT&& fn, ARGS&&... args );
// ...
};
|
The callable object could be anything callable - a free function, a function object, a closure(lambda), a call-wrapper. The callable object could be not curried at all, or partially or fully curried with std::bind.
In general, if a short simple lambda can be written, it is preferrrable to a functor curried with
std::bind(). Closures tend to make for clearer, more easily understandable code. When used within the same translation unit, they also generate more efficient code than
std::bind() - calls through closures allow inlining,
std::bind() calls are through pointers and are typically not inlined.
However, there are several situations where a lambda would not be the most appropriate choice:
Though arbitrarily complex lambdas are possible, lambdas work best when they are short, clear, concise and purely local-context driven.
std::function<> with
std::bind() is better suited for more complex or non-local scenarios.
There is no way for a lambda to capture a move-only type by value. The workaround is to capture the move-only type via a pointer - for instance a
std::shared_ptr<std::ifstream>. Or capture by reference, when life-time-issues of the captured entity tend to complicate matters. In contrast,
std::bind() bind is powered by rvalue references; a custom functor too can be, and either would lead to cleaner code.
Though lambdas can capture const objects by reference, there is no support for capture by reference to const. A custom functor or
std::bind() can easily maintain const-correctness.
Lambdas can't be recursive. Again there is a work around - wrap the lambda in a call-wrapper like
std::function<> and then call the wrapped closure from within the unwrapped closure. And again, the code would not be pretty. A functor has no such limitations.
Lambdas are not default constructible, and can't be directly used in a context where a default constructible callable object is required. This is an error:
std::unordered_set< int, decltype( [] ( int a, int b ) { return a%10 < b%10 ; } ) > my_set ;
Finally, a C++ lambda can't be polymorphic, a functor can be either compile-time or run-time polymorphic. This is a deliberate choice; lambdas are intended to be used in simple, local contexts. There is no insurmountable technical obstacle to having language support for polymorphic lambdas; For instance,
boost::phoenix lambdas are polymorphic in nature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
#include <boost/phoenix/phoenix.hpp>
#include <string>
#include <iostream>
int main()
{
using namespace boost::phoenix::arg_names ;
auto plus = arg1 + arg2 ; // polymorphic lambda
int i = 7, j = 9 ;
double d = 3.456 ;
std::string a = "abc", b = "defghijk" ;
std::cout << plus(i,j) << ' ' << plus(i,d) << ' ' << plus(a,b) << ' '
<< plus( plus(a,b).c_str(), i ) << '\n' ;
}
|