[Fit] Purpose and Use Cases

Hello, Before the formal review begins I would really like to understand the purpose of Boost.FIT It isn't clear to me from the documentation what are the real world use cases? What can't be done today (for example composition) with simple C++11 lambdas? I'd love to see several real world examples and use cases of Boost.FIT - because at least for me I don't really understand why it is useful. Artyom

It isn't clear to me from the documentation what are the real world use cases?
Well, I go over some simple use cases in quick start guide, maybe its not entirely clear. As the Fit library is a collection of generic utilities related to functions, there is many useful cases with the library, but a key point of many of these utilities is that they can solve these problems with much simpler constructs than whats traditionally been done with metaprogramming. Initialization -------------- The `BOOST_FIT_STATIC_FUNCTION` will help initialize function objects at global scope, and will ensure that it is initialized at compile-time and (on platforms that support it) will have a unique address across translation units, thereby reducing executable bloat and potential ODR violations. In addition, `BOOST_FIT_STATIC_LAMBDA_FUNCTION` allows initializing a lambda in the same manner. This allows for simple and compact function definitions when working with generic lambdas and function adaptors. Of course, the library can still be used without requiring global function objects for those who prefer to avoid them will still find the library useful. Conditional overloading ----------------------- Take a look at this example of defining a `stringify` function from stackoverflow here: http://stackoverflow.com/questions/30189926/metaprograming-failure-of-functi... The user would like to write `stringify` to call `to_string` where applicable and fallback on using `sstream` ot convert to a string. Most of the top answers usually involve some amount of metaprogramming using either `void_t` or `is_detected`. However, with the Fit library it can simply be written like this: BOOST_FIT_STATIC_LAMBDA_FUNCTION(stringify) = conditional( [](auto x) BOOST_FIT_RETURNS(to_string(x)), [](auto x) BOOST_FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str()) ); In addition, this can be used with `fit::if_` to create static if-like constructs. For example, Baptiste Wicht discusses how one could write static if in C++ here: http://baptiste-wicht.com/posts/2015/07/simulate-static_if-with-c11c14.html He wants to be able to write this: template<typename T> void decrement_kindof(T& value){ static_if(std::is_same<std::string, T>::value){ value.pop_back(); } else { --value; } } However, that isn't possible in C++. With the Fit library one can simply write this: template<typename T> void decrement_kindof(T& value) { eval(conditional( if_(std::is_same<std::string, T>())([&](auto id){ id(value).pop_back(); }), [&](auto id){ --id(value); } )); } The advantage of using the Fit library instead of the solution in Baptiste Wicht's blog is `conditional` allows more than just two conditions. So if there was another trait to be checked, such as `is_stack`, it could be written like this: template<typename T> void decrement_kindof(T& value) { eval(conditional( if_(is_stack<T>())([&](auto id){ id(value).pop(); }), if_(std::is_same<std::string, T>())([&](auto id){ id(value).pop_back(); }), [&](auto id){ --id(value); } )); } Furthermore, this technique can be used to write type traits as well. Jens Weller was looking for a way to define a general purpose detection for pointer operands(such as `*` and `->`). One way to accomplish this is using Fit like this: template<class T> auto has_pointer_member(const T&) -> decltype( &T::operator*, &T::operator->, std::true_type{} ); BOOST_FIT_STATIC_LAMBDA_FUNCTION(has_pointer_operators) = conditional( BOOST_FIT_LIFT(has_pointer_member), [](auto* x) -> -> bool_constant<(!std::is_void<decltype(*x)>())>, always(std::false_type) ); template<class T> struct is_dereferenceable : decltype(has_pointer_operators(std::declval<T>())) {} Which is much simpler than the other implementations that were written(it was about 3 times the amount of code): https://gist.github.com/lefticus/6fdccb18084a1a3410d5 Projections ----------- Instead of writing the projection multiple times in algorithms: std::sort(std::begin(people), std::end(people), [](const Person& a, const Person& b) { return a.year_of_birth < b.year_of_birth; }); We can use the `by` adaptor to project `year_of_birth` on the comparison operator: std::sort(std::begin(people), std::end(people), by(&Person::year_of_birth, _ < _)); Ordering evaluation of arguments -------------------------------- When we write `f(foo(), bar())` there is no guarantee from the standard what order the arguments `foo()` and `bar()` are evaluated. So with `apply_eval` we can order them from left-to-right: apply_eval(f, [&]{ return foo(); }, [&]{ return bar(); }); Varidiac parameters ------------------- As shown in the guide the `by` adaptor can be used to apply a function to each argument, so we could write a simple varidiac print function like this: BOOST_FIT_STATIC_FUNCTION(print) = by(std::cout << _); We can also take binary functions and turn them easily into varidiac functions as well using `compress`. So a varidiac `max` function could be written like this: BOOST_FIT_STATIC_FUNCTION(max) = compress(BOOST_FIT_LIFT(std::max)); Polymorphic constructors ------------------------ Writing polymorphic constructors(such as `make_tuple`) is a boilerplate that has to be written over and over again for template classes. With `construct` this is easier. For example, `make_tuple` can be written simply as this: BOOST_FIT_STATIC_FUNCTION(make_tuple) = construct<std::tuple>().by(decay()); Extension methods ----------------- Chaining many function together, like what is done for range based libraries can make things hard to read: auto r = transform( filter( numbers, [](int x) { return x > 2; } ), [](int x) { return x * x; } ); It would be nice to write this: auto r = numbers .filter([](int x) { return x > 2; }) .transform([](int x) { return x * x; }); However, UFCS in C++17 won't allow this to be done generically. So instead pipable functions can be used. So it can be written like this: auto r = numbers | filter([](int x) { return x > 2; }) | transform([](int x) { return x * x; }); Now, if some users feel a little worried about overloading the bit or operator, pipable functions can also be used with flow like this: auto r = flow( filter([](int x) { return x > 2; }), transform([](int x) { return x * x; }) )(numbers); No fancy or confusing operating overloading and everything is still quite readable.
What can't be done today (for example composition) with simple C++11 lambdas?
First, its not a replacement for lambdas, but rather it builds on top of lambdas(and function objects) in C++, especially in C++14. Some of the limitations of lambdas in C++11 of course, is that they are not generic, and they don't support constexpr(not even in C++14). The Fit library provides a small lambda emulation using the `lazy` adaptor(like what is done with `std::bind` or Boost.Lambda) with the main purpose to support some simple `constexpr` lambda expressions. Of course, it is not a full emulation like what is done in Boost.Lambda or Boost.Phoenix. However, I have found that in practice emulated-lambda expressions are generally only useful for small simple expressions.
I'd love to see several real world examples and use cases of Boost.FIT because at least for me I don't really understand why it is useful.
Well hopeful between this and the quick start guide, you can see some of the usefulness of the library. Let me know if you have more questions. Paul

On Tue, Feb 16, 2016 at 6:39 PM, Paul Fultz II <pfultz2@yahoo.com> wrote:
It isn't clear to me from the documentation what are the real world use cases?
Well, I go over some simple use cases in quick start guide, maybe its not entirely clear. As the Fit library is a collection of generic utilities related to functions, there is many useful cases with the library, but a key point of many of these utilities is that they can solve these problems with much simpler constructs than whats traditionally been done with metaprogramming.
[snip lots] Paul, this is good stuff. Please add it to the documentation! Zach

On 2/16/16 4:39 PM, Paul Fultz II wrote:
It isn't clear to me from the documentation what are the real world use cases?
Well, I go over some simple use cases in quick start guide, maybe its not entirely clear. As the Fit library is a collection of generic utilities related to functions, there is many useful cases with the library, but a key point of many of these utilities is that they can solve these problems with much simpler constructs than whats traditionally been done with metaprogramming.
Initialization -------------- <snip>
Looks like you invested a lot of effort into the answer. I'm wonderin if the return on your investment wouldn't have been better had you enhanced the documentation with it then just responded with a pointer to the new documentation. My personal view is that the main obstacle to many libraries passing review is that the sparse documentation makes them hard to understand and review. Robert Ramey
participants (4)
-
Artyom Beilis
-
Paul Fultz II
-
Robert Ramey
-
Zach Laine