DGtal 1.4.0
|
Part of the Base package.
This part of the manual lists some of the available functors in DGtal and also describes how to use functions, functors, lambdas, or any callable objects as DGtal compatible functors.
Starting with C++11, we can define functions directly in a code block, just before using it, using lambdas
. For small and single-used functions, it may allow to write clearer code by keeping all important informations together and avoids defining the function out of context.
However, lambdas cannot be directly used in DGtal since many functors must comply with concepts that depends on its usage. These concepts may require to define some typedef and always require that the model is assignable. Since lambdas don't meet any of these constraints, it forces the coder to define a specific class.
Starting with DGtal 1.0, FunctorHolder and its derivatives (see Which derivative class of FunctorHolder should I use ?) are available and allow to use lambdas (and other callable object) in an easier way.
First, store your lambda in an holder that matches the targeted usage:
The usage of the auto
keyword is here mandatory and the reason why is explained in Why using the auto keyword ?.
Then, use the created functor with you favorite algorithm:
The decltype
specifier is used to deduce the type of the functor, see Passing a FunctorHolder as a parameter.
Finally, export the result:
It has never been so easy to calculate the distance map to Mickey!
Given any type of callable object (function, functor and lambda) passed by value, reference or pointer, FunctorHolder stores it in the most appropriate way so that the returned object is a model of DGtal functor concept (concepts::CUnaryFunctor so far), and especially the boost::Assignable concept.
More precisely, the storage mode depends on the lifetime of the given object, deduced from its passing mode:
std::reference_wrapper
).std::shared_ptr
) so that the object lifetime matches the FunctorHolder lifetime.In both case, the returned object is copy/move constructible and copy/move assignable so that it can be used as a functor in all DGtal library.
std::move
. As a consequence, the type of the returned FunctorHolder cannot be guessed easily and the use of the auto
keyword is thus mandatory.The first motivation is to be able to use lambdas as functors in DGtal but the problem is that all functor's concepts inherits from boost::Assignable and a lambda is unfortunately not assignable.
A solution would be to rely on std::function
but this solution has two main issues:
double (*) (int)
for a function returning a double
from an int
. This implies that the compiler cannot inline the implementation of the callable object (since a pointer like double (*) (int)
can point to any compatible function) and thus cannot apply some important optimizations like vectorizing the computation.On the other hand, FunctorHolder also relies on a pointer in order to make any callable object assignable, but the pointer type is directly related to the callable object type. Therefore, even if the compiler doesn't know the address during the compilation process, since he actually know the type of the callable object, he also know its implementation and can then inline it and optimize it.
double (*) (int)
) and thus prevents the compiler to inline it (even when using FunctorHolder). To avoid this, you can wrap the function into a lambda before storing it into a FunctorHolder, like explained in the section below about Holding a function .For example, on a modern processor and if your functor only adds values, you can have a performance ratio of about 26.7 between using a std::function
and FunctorHolder. Even with more complex operations, there is still a significant performance penalty to use std::function
over FunctorHolder.
Depending on the concept you want to fullfil, here are the available classes that rely on FunctorHolder:
As warned before, FunctorHolder is not meant to be directly constructed but instead through the helper (factory) holdFunctor. You can hold any type of callable object: a function, a functor, a lambda function,...
If you want to refer to an existing function, you can directly pass its name to holdFunctor:
If the function is templated, you must specify the needed templates:
In both cases, the function will be passed by reference. You can also explicitly pass it by pointer using the &
keyword.
If you want to refer to a functor, you can pass it by (left-value) reference:
You wan also inline the construction of the functor directly in the holdFunctor call:
or, to increase code readability, you can first construct the functor and then transfer its ownership to holdFunctor by using the move semantic:
Without surprise, holding a lambda works the same way:
FunctorHolder should be able to hold any callable object. However, as warned before, if you are concerned by performance, you should avoid holding a function by reference or pointer or, even worse, holding a std::function
that is more or less a pointer with an additional cost due to the polymorphism.
When passing a functor to holdFunctor by lvalue reference (ie the functor has a name), the functor lifetime must exceed the FunctorHolder lifetime.
Otherwise, consider constructing the functor directly during the holdFunctor call or transfer its ownership by using the move semantic. See the examples in section Holding a functor.
Since the exact storage type used in FunctorHolder is choosen by holdFunctor depending on the passing mode of the callable object, it is not easily possible to known the template parameters of FunctorHolder.
Thus, it is recommended to use the auto
keyword as the type placeholder for any instance of FunctorHolder.
See also the section about Storing a FunctorHolder.
Calling the held object is done naturally by using the operator()
, like in the previous examples:
You may have notice that we never have to specify the types of the parameters used when calling the held object, neither the type of the returned object.
The trick behind this is the use of variadic templates and perfect forwarding so that the call of the held object is transparent for FunctorHolder. The returned value type is also automatically deduced.
The use of variadic templates for the operator()
allows holding a callable object of any arity:
A FunctorHolder instance is copyable, movable and assignable, thus making it a boost::Assignable model and of any other concept that trivially inherit from it (e.g. concepts::CUnaryFunctor).
std::shared_ptr
. Copying or assigning the resulting FunctorHolder is like copying or assigning the underlying std::shared_ptr
: As explained before (see Why using the auto keyword ?), you cannot easily guess the result type of a holdFunctor call. Moreover, it becomes impossible when passing a lambda in an inline way.
Thus, it is recommended to use the auto
keyword as the type placeholder for any instance of FunctorHolder:
However, when passing a FunctorHolder, especially to a class constructor, you may still need to known the FunctorHolder exact type (including the template parameters).
In those cases, a solution is to first store the FunctorHolder and then to deduce its type by using the decltype
keyword:
To ease such usage, you may want to search if there exist an helper (or factory) for that class (see also Creating a helper).
The most tricky part begins when you need a function to return a FunctorHolder.
The problem comes from the fact that up to C++11 standard, you need to somehow specify the function's return type. In C++11, you can slightly delay this type specification using the trailing return syntax but the type still needs to be known in the signature. Basically, you need to duplicate the line of code that generates the FunctorHolder (optionaly using the function's parameters) into the function signature and deduce its type using decltype
:
If it is easier to get the return type using the actual parameters, you can use the trailing return syntax:
std::declval
function that constructs a fake instance of any given type: Starting with C++14 standard, you can simply use the auto
keyword as a return type and the compiler should deduce the actual type from the return
statements of the function:
Usage of FunctorHolder (as other kind of objects whose type is difficult to guess) can be simplified by adding helpers (or factories) to classes whose template parameters depend on such objects.
A helper is only a templated function that benefits from the auto-deduction of template parameters in order to deduce the appropriate class type:
For more complex classes, like DGtal::functors::PointFunctorPredicate that use DGtal::ConstAlias in the constructor parameters, you cannot simply use DGtal::ConstAlias in the factory and hope that the compiler will deduce the aliases type:
The problem here is that implicit conversions (like the one needed from PointFunctor
to ConstAlias<PointFunctor>
) are ignored during the template deduction step. In this case, the first solution is to remove the DGtal::ConstAlias from the helper signature:
Another problem arises here: the constructor of DGtal::functors::PointFunctorPredicate will get as parameters only left-value references because the parameters have a name in the factory. Thus, you might miss some optimizations for right-value references.
In order to make a factory that doesn't change the parameters type, you must use forwarding references (by using &&
references together with template paremeter deduction, also known as universal references) and perfect forwarding using std::forward
:
Note the use of std::decay
because the template parameter will be deduced with an included reference specification that you don't want to be part of the returned class specification (std::decay
removes reference and constness from a given type).
In the following sections, we will explain how to create new DGtal functor models using FunctorHolder as an internal storage in order to accept any kind of callable objects (lambdas included).
You may want to add such classes because some concepts derived from DGtal::concepts::CUnaryFunctor may need additional data or typedef, like DGtal::concepts::CPointFunctor.
In this section, we will explain how to write a concepts::CPointFunctor model that is basically a concepts::CUnaryFunctor model with additional typedef (the point and value types).
The resulting class is more or less the PointFunctorHolder class which source code is visible in PointFunctorHolder.h .
A basic implementation of such a class would be:
There is nothing special to say about this first draft except that there is no references to FunctorHolder because it is the helper (factory) that will actually choose FunctorHolder as the TFunctor template parameter of this class.
Before continuing, you should read the previous section about Creating a helper .
The helper of the above draft should be:
Here we use the trailing return type (auto
as returned type, followed by ->
) in order to choose the TFunctor template parameter type of our class depending on the result of holdFunctor.
As explained before, this type is not easily guessable and it is why we use the decltype
keyword.
Also note the perfect forwarding syntax (using universal references and std::forward
) to avoid modifying the actual type of the given callable object (particulary keeping lvalue and rvalue references).
->
) could be removed.That's it ! You can enjoy using this concepts::CPointFunctor model, for example to hold a lambda returning the point norm:
Since the functor's return type can be easily deduced by calling it with a point, we can then provide an additional helper that needs only one template parameter:
It is a little bit more tricky: we use std::declval
in order to generate a fake point, we give it as a parameter to the functor and we deduce the result type using decltype
. The use of std::decay
allows us to remove any reference and const specifier from the deduced type.
The usage is very similar:
You may have notice that the constructor accepts the functor by constant reference. Since this functor will be a FunctorHolder, it shouldn't be a problem because copying a FunctorHolder is costless.
However, if you want more genericity in order to use another storage mode, you should consider using perfect forwarding in the constructor:
so that the transfer from the given functor to its storage is unaffected.
However, the problem is that the compiler may choose this constructor as a better overload for the copy constructor, even if you define it: for example, if you copy construct from a mutable PointFunctorHolder, the perfect forwarding constructor will be a better choice than the copy construction from a constant reference.
One of the possible solutions to avoid this is to disable this constructor when the given type is related to the current class. To do this, we can rely on SFINAE.
std::enable_if
structure and the trailing return type.decltype
is not the actual return type, you can use the comma operator to specify an additional expression: So here is a viable solution:
that uses std::is_base_of
type traits to check if the given parameter is a or inherits from PointFunctorHolder.
When documenting such a class, it is important to warn the user about two things:
In addition, for performance reason, you may remark that functions should be wrapped into a lambda before being stored into your class, as explained in the section about Holding a function .
Finally, you should link to this module page for more informations.
In this section, we will explain how to write a class that may hold an unary or binary functor. It will be based on the ConstImageFunctorHolder class that transforms a given callable object into a concepts::CConstImage model.
Basically, to define an image, a concepts::CPointFunctor model (i.e a functor that returns a value from a given point) would be enough but we want to allow the user to pass a functor that also depends on the image's domain (e.g. when calculating distance to the bounds).
To do so, using SFINAE, we can detect the arity of the given functor and even its parameter's types.
Here is the core skeleton of the ConstImageFunctorHolder class, without the operator()
that will be introduced later:
Note that using perfect forwarding in the constructor doesn't imply here to add SFINAE like in Perfect forwarding in the constructor because a binary constructor cannot be choosen as a valid overload during a copy construction.
The helper is very similar to the one introduced in The helper (factory):
Here, in order to take into account the fact that the held functor may by unary (accepting only a point) or binary (accepting a point and the image's domain), we must rely on the SFINAE trick explained in Perfect forwarding in the constructor so that the compiler chooses the right implementation:
Here, the first overload is valid only if the myFunctor(aPoint)
expression is valid, that is if the held functor is unary and accept a point as parameter. Likewise, the second overload is valid only if the functor is binary and accept a point as the first argument and a domain as the second argument.
operator()
.If you want to automatically deduce the TValue template parameter, the solution is quite similar to the one proposed in Auto-deducing the return type of the functor but in two versions, one for each possible arity of the given functor:
Finally, an image can be easily defined from a functor with a syntax like:
resulting in: