Boost logo

Boost :

Subject: Re: [boost] Design conventions; passing functors
From: Joachim Faulhaber (afojgo_at_[hidden])
Date: 2008-11-15 15:11:26


2008/11/11 David Abrahams <dave_at_[hidden]>:
>
> on Thu Nov 06 2008, "Joachim Faulhaber" <afojgo-AT-googlemail.com> wrote:
>
>> template<class Domain, class Codomain, ...>
>> class interval_map{
>> ...
>> //(1) My current implementation is this:
>> // Combinator is the aggregating functor template,
>> // instantiated at compiletime
>> template<template<class>class Combinator>
>> interval_map& add(const value_type& value_pair)
>> { /*Combinator template passed or instantiated somewhere*/ }
>> ...
>> }
>>
>> //I like this definition, because it's abstract:
>> // The functor has to be a matching template e.g. inplace_max
>> interval_map<int,int> m;
>> m.add<inplace_max>(some); //(1)
>>
>> template<class Domain, class Codomain, ...>
>> class interval_map{
>> //(2) The more traditional definition
>> template<class Combinator>
>> interval_map& add(const value_type& value_pair)
>> { /*Combinator type passed or called somewhere*/ }
>> ...
>>
>> m.add<inplace_max<int> >(some); //(2)
>>
>> I prefer (1) but both definitions (1) and (2) are purely
>> static denotations. To have adaptable functors or functors
>> that can have states, like in ...
>
>
> Try a unary MPL lambda expression here instead:
>
> m.add<inplace_max<_> >(some);
>
This is very smart. It took me a while to get it ;-)

So I have alternative implementation (4) now:

template<class Domain, class Codomain, ...>
class interval_map{
   //(4) Using mpl::apply
  template<class Combinator>
  interval_map& add(const value_type& value_pair)
  { ...
    mpl::apply<Combinator,CodomainT>::type()(x,y);
    ...
  }

I wonder whether every template template parameter
instantiation can be expressed by an equivalent mpl
lambda expression in general!

// Unary template template parameter expressed via mpl lambda
template<class T>struct unary{
  void speek(){ cout<<"unary"<<endl; }
};

template<class T, template<class>class U>
struct unator1{
  void speek(){U<T> obj; obj.speek();}
};

template<class T, class U> //equivalent to unator1
struct unator2{
  void speek(){
    mpl::apply<U,T>::type obj;
    obj.speek();
  }
};

// Binary template template parameter expressed via mpl lambda
template<class T1, class T2>struct binary{
  void speek(){ cout<<"binary"<<endl; }
};

template<class T1, class T2,
         template<class,class>class U>
struct binator1{
  void speek(){U<T1,T2> obj; obj.speek();}
};

template<class T1, class T2, class U>
struct binator2{ //equivalent to binator1
  void speek(){
    mpl::apply2<U,T1,T2>::type obj;
    obj.speek();
  }
};

// Ternary ... and so on

int main(){
  unator1<int, unary> untor1; untor1.speek();
  unator2<int, unary<_> > untor2; untor2.speek();
  binator1<int, int, binary> bintor1; bintor1.speek();
  binator2<int, int, binary<_1,_2> > bintor2; bintor2.speek();
  //...
}

>> template<class Domain, class Codomain, ...>
>> class interval_map{
>> //(3) Adaptable functor
>> template<template<class>class Combinator>
>> interval_map& add(const value_type& value_pair,
>> const Combinator<Codomain>& combine)
>> { /*combine functor passed or called somewhere*/ }
>
> Presumably because you want to support stateful combine functions?

Yes, especially to allow clients of the library to simulate partial
function evaluation, that is known from functional languages.
Like in partial function evaluation I think it is sufficient to set a
state that is then constant, like in:

SomeSet my_set; get(my_set);
// add_if_in: SomeSet -> (CodomainT x CodomainT) -> CodomainT
// first argument partially evaluated
m.add(some, add_if_in(my_set));

>
>> Now, there seems to be a convention from the STL that functors
>> shall be passed BY VALUE.
>
> Ja.
>
>> template<template<class>class Combinator>
>> interval_map& add(const value_type& value_pair,
>> Combinator<Codomain> combine);
>>
>> This is stated in [Sutter & Alexandrecu 2005, C++ Coding
>> Standards] item 89:
>> "Function objects are modeled on function pointers. Like
>> function pointers, the convention is to pass them by value.
>> All of the standard algorithms pass objects by value, and
>> your algorithms should too."
>>
>> I do not really understand this convention and feel some
>> resistance to follow it. In addition the call by value
>> implementation can (and will) lead to heavy inefficiency
>> by unaware usage of fat functors.
>
> Sounds like a job for move semantics.

But the job has to be done by the clients of my library code?
The functors they write and pass to itl::add have to implement
move semantics. Did I get that right?
In this case, the burdon of knowing all the c++ intricacies is
on the libraries clients not on it's author.
I am afraid that is a problem.

Thanx for all the answers,
Joachim


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk