Skip to content

Commit

Permalink
Issue #153: Add examples section
Browse files Browse the repository at this point in the history
  • Loading branch information
pfultz2 committed Mar 29, 2016
1 parent 19a05c7 commit 4ab5923
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 117 deletions.
118 changes: 118 additions & 0 deletions doc/src/example_overloading.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
Conditional overloading
=======================

Conditional overloading takes a set of functions and calls the first one that is callable. This is one of the ways to resolve ambiguity with overloads, but more importantly it allows an alternative function to be used when the first is not callable.

Stringify
---------

Take a look at this example of defining a `stringify` function from
stackoverflow [here](http://stackoverflow.com/questions/30189926/metaprograming-failure-of-function-definition-defines-a-separate-function/30515874).

The user would like to write `stringify` to call `to_string` where applicable
and fallback on using `sstream` to convert to a string. Most of the top
answers usually involve some amount of metaprogramming using either `void_t`
or `is_detected`(see [n4502](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf)). However, with the Fit library it can simply be written like
this:

FIT_STATIC_LAMBDA_FUNCTION(stringify) = conditional(
[](auto x) FIT_RETURNS(to_string(x)),
[](auto x) FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str())
);

So, using [`FIT_RETURNS`](returns.md) no only deduces the return type for the function, but it also constrains the function on whether the expression is valid or not either. So by writing `FIT_RETURNS(to_string(x))` then the first function will try to call `to_string` function if possible. If not, then the second function will be called.

The second function still uses `FIT_RETURNS`, so the function will still be constrained by whether the `<<` stream operator can be used. Although it may seem unnecsarry because there is not another function, however, this makes the function composable. So we could use this to define a `serialize` function that tries to call stringify first, otherwise it looks for the member `.serialize()`:

FIT_STATIC_LAMBDA_FUNCTION(serialize) = conditional(
[](auto x) FIT_RETURNS(stringify(x)),
[](auto x) FIT_RETURNS(x.serialize())
);

static_if
---------

In addition, this can be used with the [`fit::if_`](if.md) decorator 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 `id` parameter passed to the lambda is the [`identity`](identity.md) function. As explained in the article, this is used to delay the lookup of types by making it a dependent type(i.e. the type depends on a template parameter), which is necessary to avoid compile errors.

The advantage of using the Fit library instead of the solution in Baptiste
Wicht's blog, is that [`conditional`](conditional.md) 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);
}
));
}

Type traits
-----------

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:

// Check that T has member function for operator* and ope
template<class T>
auto has_pointer_member(const T&) -> decltype(
&T::operator*,
&T::operator->,
std::true_type{}
);

FIT_STATIC_LAMBDA_FUNCTION(has_pointer_operators) = conditional(
FIT_LIFT(has_pointer_member),
[](auto* x) -> bool_constant<(!std::is_void<decltype(*x)>())> { return {}; },
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, which were
about 3 times the amount of code(see [here](https://gist.github.com/lefticus/6fdccb18084a1a3410d5)).

The `has_pointer_operators` function works by first trying to call `has_pointer_member` which returns `true_type` if the type has member functions `operator*` and `operator->`, otherwise the function is not callable. The next function is only callable on pointers, which returns true if it is not a `void*` pointer(because `void*` pointers are not dereferenceable). Finally, if none of those functions matched then the last function will always return `false_type`.
32 changes: 32 additions & 0 deletions doc/src/example_polymorphic_constructors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Polymorphic constructors
========================

Writing polymorphic constructors(such as `make_tuple`) is a boilerplate that
has to be written over and over again for template classes:

template <class T>
struct unwrap_refwrapper
{
typedef T type;
};
template <class T>
struct unwrap_refwrapper<std::reference_wrapper<T>>
{
typedef T& type;
};
template <class T>
struct unwrap_ref_decay
: unwrap_refwrapper<typename std::decay<T>::type>
{};

template <class... Types>
std::tuple<typename unwrap_ref_decay<Types>::type...> make_tuple(Types&&... args)
{
return std::tuple<typename unwrap_ref_decay<Types>::type...>(std::forward<Types>(args)...);
}

The [`construct`](construct.md) function takes care of all this boilerplate, and the above can be simply written like this:

FIT_STATIC_FUNCTION(make_tuple) = construct<std::tuple>().by(decay());
26 changes: 26 additions & 0 deletions doc/src/example_variadic_parameters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Variadic parameters
===================

The library provides utilities to simplify handling variadic parameters. For example, if we want to write a variadic print function that prints each argument, like this:

print("Hello", "World");

We can write this simply by using the [`by`](by.md) adaptor:

FIT_STATIC_FUNCTION(print) = by(std::cout << _);

The [`by`](by.md) adaptor calls the function for each argument passed in. In this case, we write `std::cout << _` which uses the [placeholders](placeholders.md) to create a function that print to `std::cout`.

We can also take binary functions and turn them easily into variadic functions
using [`compress`](compress.md), which will do a fold over the variadic parameters. So a variadic `max` function could be written like
this:

FIT_STATIC_FUNCTION(max) = compress(FIT_LIFT(std::max));

[`FIT_LIFT`](lift.md) is used to grab the entire overload set of `std::max` function, which is needed since `std::max` is templated and we want the variadic `std::max` function to handle any types as well. So now it can be called like this:

auto n = max(1, 2, 4, 3); // Returns 4
auto m = max(0.1, 0.2, 0.5, 0.4); // Returns 0.5

By using [`compress`](compress.md), `max(1, 2, 4, 3)` is called like `std::max(std::max(std::max(1, 2), 4), 3)` and `max(0.1, 0.2, 0.5, 0.4)` is called like `std::max(std::max(std::max(0.1, 0.2), 0.5), 0.4)`.

116 changes: 0 additions & 116 deletions doc/src/more_examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,99 +22,6 @@ 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-function-definition-defines-a-separate-function/30515874).

The user would like to write `stringify` to call `to_string` where applicable
and fallback on using `sstream` to convert to a string. Most of the top
answers usually involve some amount of metaprogramming using either `void_t`
or `is_detected`(see [n4502](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf)). However, with the Fit library it can simply be written like
this:

FIT_STATIC_LAMBDA_FUNCTION(stringify) = conditional(
[](auto x) FIT_RETURNS(to_string(x)),
[](auto x) FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str())
);

In addition, this can be used with the [`fit::if_`](if.md) decorator 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 that [`conditional`](conditional.md) 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{}
);

FIT_STATIC_LAMBDA_FUNCTION(has_pointer_operators) = conditional(
FIT_LIFT(has_pointer_member),
[](auto* x) -> bool_constant<(!std::is_void<decltype(*x)>())> { return {}; },
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, which were
about 3 times the amount of code(see [here](https://gist.github.com/lefticus/6fdccb18084a1a3410d5)).

Projections
-----------
Expand All @@ -141,29 +48,6 @@ can order them from left-to-right:

apply_eval(f, [&]{ return foo(); }, [&]{ return bar(); });

Variadic parameters
-------------------

As shown in the [quick start guide](quickstart.md) the [`by`](by.md) adaptor can be used to apply a function to each
argument, so we could write a simple variadic print function like this:

FIT_STATIC_FUNCTION(print) = by(std::cout << _);

We can also take binary functions and turn them easily into variadic functions
using [`compress`](compress.md). So a variadic `max` function could be written like
this:

FIT_STATIC_FUNCTION(max) = compress(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`](construct.md)
this is easier. For example, `make_tuple` can be written simply as this:

FIT_STATIC_FUNCTION(make_tuple) = construct<std::tuple>().by(decay());

Extension methods
-----------------

Expand Down
6 changes: 5 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,18 @@ pages:
- Home: 'index.md'
- About:
- 'Quick Start': 'quickstart.md'
- 'More examples': 'more_examples.md'
- 'Definitions' : 'definitions.md'
- 'Basic Concepts' : 'concepts.md'
- 'Configurations' : 'configurations.md'
- 'FAQ': 'faq.md'
- 'Building': 'building.md'
- 'Acknowledgements': 'acknowledgements.md'
- 'License': 'license.md'
- Examples:
- 'Conditional overloading': 'example_overloading.md'
- 'Variadic parameters': 'example_variadic_parameters.md'
- 'Polymorphic constructors': 'example_polymorphic_constructors.md'
- 'More examples': 'more_examples.md'
- Adaptors:
- 'by': 'by.md'
- 'compose': 'compose.md'
Expand Down

0 comments on commit 4ab5923

Please sign in to comment.