DGtal 1.4.0
Loading...
Searching...
No Matches
Using functions, functors and lambdas in DGtal
Author(s) of this documentation:
Roland Denis
Since
1.0.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.

The functors in DGtal

See also
functors

The FunctorHolder class

Introduction and preliminary example

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:

auto mickey = functors::holdPointFunctor<Z2i::Point>(
[] ( Z2i::Point const& pt ) {
return
(pt - Z2i::Point(0, -5)).norm() > 3
&& ( pt.norm() <= 20
|| (pt - Z2i::Point(-18,21)).norm() <= 10
|| (pt - Z2i::Point( 18,21)).norm() <= 10
);
});

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:

Z2i::Domain domain( Z2i::Point(-35,-25), Z2i::Point(35, 35) );
using DTL2 = DistanceTransformation<Z2i::Space, decltype(mickey), Z2i::L2Metric>;
DTL2 dt( domain, mickey, Z2i::l2Metric );

The decltype specifier is used to deduce the type of the functor, see Passing a FunctorHolder as a parameter.

Finally, export the result:

DTL2::Value maxDT = *boost::first_max_element( dt.constRange().begin(), dt.constRange().end() );
Board2D aBoard;
aBoard << domain;
Display2DFactory::drawImage<HueTwice>(aBoard, dt, 0, maxDT);
aBoard.saveEPS("examplePointFunctorHolder.eps");
Distance transformation of Mickey

It has never been so easy to calculate the distance map to Mickey!

Aim

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:

  • if the given object is passed by left-value reference (long lifetime), it will be stored as a reference (using std::reference_wrapper).
  • otherwise (short lifetime, like right-value references), it will be moved to a dynamically allocated memory place (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.

Warning
This class is not meant to be directly constructed by the user. As explained below, you should use instead the holdFunctor helper that will choose the more appropriate storage type depending on the given callable object.
Remarks
It is important to not explicitly specify the given object type so that it's real lifetime can be deduced using a universal reference. However, you can transfer ownership of the object to FunctorHolder by moving it using 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.
Note
Almost all snippets used in the following explanations come from the example file exampleFunctorHolder.cpp
See also
FunctorHolder, holdFunctor

Why this class ?

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:

  • it refers to the callable object by using a generic pointer, like 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.
  • it uses polymorphism to hide the actual callable object type stored underneath. This implies an extra indirection when calling the underlying object.

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.

Note
A pointer to a function has a generic type (like 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.

Which derivative class of FunctorHolder should I use ?

Depending on the concept you want to fullfil, here are the available classes that rely on FunctorHolder:

Holding a callable object

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,...

Holding a function

If you want to refer to an existing function, you can directly pass its name to holdFunctor:

{
return pt.norm() - 1.;
}
std::cout << fn( Point(1, 1) ) << std::endl;

If the function is templated, you must specify the needed templates:

template <typename Point>
inline
typename Point::Component
{
return pt.norm() - typename Point::Component(1);
}

In both cases, the function will be passed by reference. You can also explicitly pass it by pointer using the & keyword.

Warning
However, please note that passing a function by reference or by pointer will prevent the compiler from inlining it (you may have a chance by reference).
To avoid this possible performance penalty, you should pass the function through a lambda:
auto fn = DGtal::functors::holdFunctor( [] (Point const& pt) { return signed_dist_to_unit_circle(pt); } );
std::cout << fn( Point(1, 1) ) << std::endl;
auto fn = DGtal::functors::holdFunctor( [] (Point const& pt) { return templated_signed_dist_to_unit_circle(pt); } );
std::cout << fn( Point(1, 1) ) << std::endl;
thus giving the compiler free hands to inline it.
Even better: if you have enable C++14 support, you can use generic lambdas so that you don't have to specify any type, even for templated functions (the type is resolved at the effective call):
auto fn = DGtal::functors::holdFunctor( [] (auto const& pt) { return templated_signed_dist_to_unit_circle(pt); } );
std::cout << fn( Point(1, 1) ) << std::endl; // <- template parameter is resolved to Point
Enjoy the genericity !

Holding a functor

If you want to refer to a functor, you can pass it by (left-value) reference:

template <typename Point>
struct SignedDistToCircle
{
using Real = typename Point::Component;
Point center;
Real radius;
SignedDistToCircle(Point const& pt, Real r)
: center(pt), radius(r)
{}
inline
Real operator() (Point const& pt) const
{
return (pt - center).norm() - radius;
}
};
SignedDistToCircle<Point> dist(Point(0, 1), 2);
auto fn = DGtal::functors::holdFunctor( dist );
std::cout << fn( Point(1, 1) ) << std::endl;

You wan also inline the construction of the functor directly in the holdFunctor call:

auto fn = DGtal::functors::holdFunctor( SignedDistToCircle<Point>( Point(0, 1), 2 ) );
std::cout << fn( Point(1, 1) ) << std::endl;

or, to increase code readability, you can first construct the functor and then transfer its ownership to holdFunctor by using the move semantic:

SignedDistToCircle<Point> dist(Point(0, 1), 2);
auto fn = DGtal::functors::holdFunctor( std::move(dist) );
std::cout << fn( Point(1, 1) ) << std::endl;
Remarks
Moving the functor to holdFunctor is also a way to increase its lifetime, for example when returning a FunctorHolder that depends on a local functor. See also the section Held object lifetime.

Holding a lambda

Without surprise, holding a lambda works the same way:

Point center(0, 1);
double radius = 2;
[&center, &radius] (Point const& pt) {
return (pt - center).norm() - radius;
}
);
std::cout << fn( Point(1, 1) ) << std::endl;

Holding something else

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.

Held object lifetime

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.

Why using the auto keyword ?

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

Calling the held object is done naturally by using the operator(), like in the previous examples:

auto fn = DGtal::functors::holdFunctor( SignedDistToCircle<Point>( Point(0, 1), 2 ) );
std::cout << fn( Point(1, 1) ) << std::endl;

What about the parameters and return value types ?

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.

What about the callable object arity ?

The use of variadic templates for the operator() allows holding a callable object of any arity:

auto dist = [] (Point const& pt, Point const& center, double radius)
{
return (pt - center).norm() - radius;
};
auto fn = DGtal::functors::holdFunctor( dist );
std::cout << fn( Point(1, 1), Point(0, 1), 2 ) << std::endl;

Copying and assigning a FunctorHolder

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).

Warning
When copying or assigning a FunctorHolder, the two involved instances will afterward both refer to the same callable object (i.e. the held object is not copied). For example, modifying a functor attribute after the copy will modify the result of all original and copied instances of FunctorHolder that hold it:
SignedDistToCircle<Point> dist(Point(0, 1), 2);
auto fn = DGtal::functors::holdFunctor( dist );
auto fn2 = fn;
std::cout << fn( Point(1, 1) ) << std::endl; // Output: -1
std::cout << fn2( Point(1, 1) ) << std::endl; // Output: -1
dist.radius = 3; // fn and fn2 both refer to the same functor dist.
std::cout << fn( Point(1, 1) ) << std::endl; // Output: -2
std::cout << fn2( Point(1, 1) ) << std::endl; // Output: -2
It is also the case when passing the object by rvalue reference to holdFunctor since it moves it to a dynamically allocated memory place, managed by std::shared_ptr. Copying or assigning the resulting FunctorHolder is like copying or assigning the underlying std::shared_ptr:
int init_cnt = 0;
auto fn = DGtal::functors::holdFunctor( [init_cnt] () mutable { return ++init_cnt; } );
std::cout << fn() << std::endl; // Output: 1
auto fn2 = fn;
std::cout << fn2() << std::endl; // Output: 2
std::cout << fn() << std::endl; // Output: 3
Note
A counterpart of this design it that FunctorHolder instances are lightweight.

The type of a FunctorHolder instance

Note
Note that the next topics are not specific to FunctorHolder. They may be useful in many cases where you can't or don't want to guess the result type of an expression.

Storing a FunctorHolder

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:

auto fn = DGtal::functors::holdFunctor( SignedDistToCircle<Point>( Point(0, 1), 2 ) );

Passing a FunctorHolder as a parameter

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:

auto binarizer = DGtal::functors::holdFunctor( [] (Image::Value v) { return v <= 135; } );
DGtal::functors::PointFunctorPredicate<Image, decltype(binarizer)> predicate(image, binarizer);

To ease such usage, you may want to search if there exist an helper (or factory) for that class (see also Creating a helper).

Returning a FunctorHolder

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:

template <typename T>
struct Binarizer
{
T threshold;
explicit Binarizer(T v) : threshold(v) {}
Binarizer& operator= (Binarizer const&) = delete; // This is not a model of boost::Assignable
bool operator() (T v) const { return v <= threshold; }
};
template <typename T>
inline
decltype(DGtal::functors::holdFunctor(Binarizer<T>(128))) // Deduced return type
{
return DGtal::functors::holdFunctor( Binarizer<T>(128) );
}
auto binarizer = get_trivial_binarizer<int>();

If it is easier to get the return type using the actual parameters, you can use the trailing return syntax:

template <typename Iterator>
auto get_mean_binarizer_from_range(Iterator first, Iterator last) // auto as return type
-> decltype(DGtal::functors::holdFunctor(Binarizer<decltype(*first / std::distance(first, last))>(0)))
// with trailing return type specification using ->
{
using value_type = typename std::iterator_traits<Iterator>::value_type;
auto const mean = std::accumulate(first, last, value_type(0));
auto const size = std::distance(first, last);
return DGtal::functors::holdFunctor(Binarizer<decltype(mean / size)>(mean / size));
}
auto binarizer = get_mean_binarizer_from_range(image.begin(), image.end());
DGtal::functors::PointFunctorPredicate<Image, decltype(binarizer)> predicate(image, binarizer);
Note
Note that you don't have to put the exact same expression in the trailing return type deduction and in the actual return. Like in the previous snippet, you can simply use another expression you know the result type will be the same as the actual return expression.
Going further, if writting such simplier expression is difficult, you can use std::declval function that constructs a fake instance of any given type:
template <typename Image>
auto get_mean_binarizer_from_an_image(std::string const& file_name)
std::declval<Image>().begin(), std::declval<Image>().end() ))
{
Image const image = DGtal::GenericReader<Image>::import(file_name);
return get_mean_binarizer_from_range(image.begin(), image.end());
}

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:

template <typename Iterator>
auto get_mean_binarizer_from_range_cpp14(Iterator first, Iterator last)
{
using value_type = typename std::iterator_traits<Iterator>::value_type;
auto const mean = std::accumulate(first, last, value_type(0));
auto const size = std::distance(first, last);
return DGtal::functors::holdFunctor(Binarizer<decltype(mean / size)>(mean / size));
}

Creating a helper

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:

template <typename T>
inline
Binarizer<T> makeBinarizer( T const& v ) // T auto-deduced from the parameter v
{
return Binarizer<T>(v);
}
auto binarizer = DGtal::functors::holdFunctor( makeBinarizer(135) );
DGtal::functors::PointFunctorPredicate<Image, decltype(binarizer)> predicate(image, binarizer);
Note
Starting with C++17, these helpers can be replaced by deduction guides that are custom rules for deducing class template parameters from a direct call to the constructor (without specifying the deductible template parameters):
auto binarizer = DGtal::functors::holdFunctor( Binarizer(135) ); // Binarizer template parameter is not specified.
DGtal::functors::PointFunctorPredicate<Image, decltype(binarizer)> predicate(image, binarizer);

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:

template <
typename PointFunctor,
typename Predicate
>
makePointFunctorPredicate_Example( PointFunctor const& aFun, Predicate const& aPred )
{
}
auto binarizer = DGtal::functors::holdFunctor( makeBinarizer(135) );
auto predicate = makePointFunctorPredicate_Example( image, binarizer );

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:

template <
typename PointFunctor,
typename Predicate
>
typename std::decay<PointFunctor>::type,
typename std::decay<Predicate>::type
>
makePointFunctorPredicate_Example2( PointFunctor && aFun, Predicate && aPred )
{
typename std::decay<PointFunctor>::type,
typename std::decay<Predicate>::type
>(
std::forward<PointFunctor>(aFun),
std::forward<Predicate>(aPred)
);
}

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).

Making a C(Unary)Functor model based on FunctorHolder

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.

A simple CUnaryFunctor model with additional typedef

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 .

The core skeleton

A basic implementation of such a class would be:

template <
typename TPoint,
typename TValue,
typename TFunctor
>
class PointFunctorHolder
{
public:
// DGtal types
using Point = TPoint;
using Value = TValue;
using Functor = TFunctor;
// Storing the functor
explicit PointFunctorHolder(TFunctor const& fn)
: myFunctor(fn)
{}
// Evaluating the functor
inline Value operator() ( Point const& aPoint ) const
{
return myFunctor( aPoint );
}
private:
Functor myFunctor;
};
PointFunctorHolder(Function &&fn)
Constructor.
Value operator()(Point const &aPoint) const
Evaluates the functor at the given point.
PointFunctorHolder< TPoint, TValue, TFunctor > Self
MyPointD Point
const Point aPoint(3, 4)

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.

The helper (factory)

Before continuing, you should read the previous section about Creating a helper .

The helper of the above draft should be:

template <
typename TPoint,
typename TValue,
typename TFunctor
>
inline auto
holdPointFunctor( TFunctor && aFunctor )
-> PointFunctorHolder<TPoint, TValue, decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))>
{
return PointFunctorHolder<TPoint, TValue, decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))>{
holdFunctor(std::forward<TFunctor>(aFunctor))
};
}
auto holdPointFunctor(TFunctor &&aFunctor) -> PointFunctorHolder< TPoint, TValue, decltype(holdFunctor(std::forward< TFunctor >(aFunctor)))>
PointFunctorHolder construction helper with specification of the return type.
auto holdFunctor(Function &&fn) -> decltype(holdFunctorImpl(std::forward< Function >(fn), typename std::is_lvalue_reference< Function >{}))
Hold any callable object (function, functor, lambda, ...) as a C(Unary)Functor model.

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).

Remarks
There is many repetitions in this helper. Starting with C++14 standard, the trailing return type (after the ->) could be removed.

First test

That's it ! You can enjoy using this concepts::CPointFunctor model, for example to hold a lambda returning the point norm:

using Point = PointVector<2, int>;
[] (Point const& pt) { return pt.norm(); }
);
BOOST_CONCEPT_ASSERT( (concepts::CPointFunctor<decltype(fn)>) );

Auto-deducing the return type of the functor

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:

template <
typename TPoint,
typename TFunctor
>
inline auto
holdPointFunctor( TFunctor && aFunctor )
-> PointFunctorHolder<
TPoint,
typename std::decay<decltype(aFunctor(std::declval<TPoint>()))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>
{
return PointFunctorHolder<
TPoint,
typename std::decay<decltype(aFunctor(std::declval<TPoint>()))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>{ holdFunctor(std::forward<TFunctor>(aFunctor)) };
}

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:

using Point = PointVector<2, int>;
auto fn = holdPointFunctor<Point>( // Value template parameter deduced to double
[] (Point const& pt) { return pt.norm(); }
);
BOOST_CONCEPT_ASSERT( (concepts::CPointFunctor<decltype(fn)>) );

Perfect forwarding in the constructor

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:

template < typename Function >
explicit PointFunctorHolder(Function && fn)
: myFunctor(std::forward<Function>(fn))
{}
STL namespace.

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.

Note
The idea behind SFINAE (Substitution Failure Is Not An Error) is that, during the phase when the compiler lists all possible overloads that may fit a given call, if substituting the template parameters of a considered function fails (i.e. the function signature becomes invalid), then this specialization is discarded without generating a compilation error.
So, the idea is to modify the function signature so that it becomes invalid for some types. To do so, we can add some code in:
  • the template parameters declaration,
  • the arguments list,
  • the return type.
This trick works before C++11 but this standard makes it easier with the std::enable_if structure and the trailing return type.
Using the trailing return type, you can easily disable a function overload if a given expression is invalid:
template <typename T>
auto do_something_if_addable(T v) // Considered only if v is addable
-> decltype(v + v)
{
...
}
If the type deduced by decltype is not the actual return type, you can use the comma operator to specify an additional expression:
template <typename T>
auto do_something_if_addable(T v) // Considered only if v is addable
-> decltype(v + v, void()) // This function doesn't return
{
...
}

So here is a viable solution:

template <
typename Function,
// SFINAE trick to disable this constructor in a copy/move construction context.
typename std::enable_if<!std::is_base_of<PointFunctorHolder, typename std::decay<Function>::type>::value, int>::type = 0
>
explicit PointFunctorHolder(Function && fn)
: myFunctor(std::forward<Function>(fn))
{}

that uses std::is_base_of type traits to check if the given parameter is a or inherits from PointFunctorHolder.

Documenting

When documenting such a class, it is important to warn the user about two things:

  1. the class is not meant to be used directly but only through the associated helper or factory.
  2. a copied instance of this class will point to the exact same underlying object as the original instance.

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.

A more complex example with variable functor arity

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.

The core skeleton

Here is the core skeleton of the ConstImageFunctorHolder class, without the operator() that will be introduced later:

template <
typename TDomain,
typename TValue,
typename TFunctor
>
class ConstImageFunctorHolder
{
public:
// DGtal types
using Domain = TDomain;
using Point = typename Domain::Point;
using Vector = typename Domain::Vector;
using Value = TValue;
using Functor = TFunctor;
// Ranges and iterators
using ConstIterator = boost::transform_iterator< std::reference_wrapper<const Self>, typename Domain::ConstIterator >;
using ConstReverseIterator = std::reverse_iterator< ConstIterator >;
class ConstRange; // To be defined...
// Constructor
template < class TGivenFunctor >
explicit ConstImageFunctorHolder( Domain const& aDomain, TGivenFunctor && aFunctor )
: myDomain( aDomain )
, myFunctor( std::forward<TGivenFunctor>(aFunctor) )
{}
// Return the associated domain.
inline Domain const& domain() const
{
return myDomain;
}
private:
Functor myFunctor;
};
Aim: model of CConstBidirectionalRangeFromPoint that adapts any range of elements bounded by two iter...
ConstImageFunctorHolder(Domain const &aDomain, TGivenFunctor &&aFunctor)
Constructor.
Domain const & domain() const
Returns the associated domain.
std::reverse_iterator< ConstIterator > ConstReverseIterator
ConstImageFunctorHolder< TDomain, TValue, TFunctor > Self
Functor myFunctor
The functor that generates the image.
DigitalPlane::Point Vector
MyDigitalSurface::ConstIterator ConstIterator
HyperRectDomain< Space > Domain

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 (factory)

The helper is very similar to the one introduced in The helper (factory):

template <
typename TValue,
typename TDomain,
typename TFunctor
>
inline auto
holdConstImageFunctor( TDomain const& aDomain, TFunctor && aFunctor )
-> ConstImageFunctorHolder<TDomain, TValue, decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))>
{
return ConstImageFunctorHolder<TDomain, TValue, decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))>{
aDomain, holdFunctor(std::forward<TFunctor>(aFunctor))
};
}
auto holdConstImageFunctor(TDomain const &aDomain, TFunctor &&aFunctor) -> ConstImageFunctorHolder< TDomain, TValue, decltype(holdFunctor(std::forward< TFunctor >(aFunctor)))>
ConstImageFunctorHolder construction helper with specification of the return type.

The evaluation operator

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:

template <typename TPoint> // Needed template parameter to enable SFINAE trick
inline
auto operator() ( TPoint const& aPoint ) const
-> decltype( myFunctor( aPoint ) ) // Using SFINAE to enable this overload for unary functor
{
return myFunctor( aPoint );
}
template <typename TPoint> // Needed template parameter to enable SFINAE trick
inline
auto operator() ( TPoint const& aPoint ) const
-> decltype( myFunctor( aPoint, myDomain ) ) // Using SFINAE to enable this overload for binary functor
{
return myFunctor( aPoint, myDomain );
}

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.

Note
If the functor is unary and binary at the same time, this will lead to a compilation error. However, if it makes sense to handle such functors, it is possible to prioritize the two overloads.
Warning
SFINAE trick needs to know the myDomain member at the method declaration. Thus, it is important to declare myDomain before declaring the two overloads of operator().

Auto-deducing the return type of the functor

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:

// Helper for a unary functor
template <
typename TDomain,
typename TFunctor
>
inline auto
holdConstImageFunctor( TDomain const& aDomain, TFunctor && aFunctor )
-> ConstImageFunctorHolder<
TDomain,
typename std::decay<decltype(aFunctor(std::declval<typename TDomain::Point>()))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>
{
return ConstImageFunctorHolder<
TDomain,
typename std::decay<decltype(aFunctor(std::declval<typename TDomain::Point>()))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>{ aDomain, holdFunctor(std::forward<TFunctor>(aFunctor)) };
}
// Helper for a binary functor
template <
typename TDomain,
typename TFunctor
>
inline auto
holdConstImageFunctor( TDomain const& aDomain, TFunctor && aFunctor )
-> ConstImageFunctorHolder<
TDomain,
typename std::decay<decltype(aFunctor(std::declval<typename TDomain::Point>(), aDomain))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>
{
return ConstImageFunctorHolder<
TDomain,
typename std::decay<decltype(aFunctor(std::declval<typename TDomain::Point>(), aDomain))>::type,
decltype(holdFunctor(std::forward<TFunctor>(aFunctor)))
>{ aDomain, holdFunctor(std::forward<TFunctor>(aFunctor)) };
}

Finally, an image can be easily defined from a functor with a syntax like:

const Domain domain1(Point(1,1), Point(16,16));
domain1,
[] (Point const& pt) { return 25 * ( std::cos( (pt - Point(4,4)).norm() ) + 1 ); }
);

resulting in:

Image generated from a point-dependent lambda.