How do you chain functions with arrow pointers?



like this example?


mars->foo1()->foo2()->foo3();

how do you chain function with arrow pointers?

and in a practical example not just in theoretical
Last edited on
This is part of what is called the named parameter idiom.
The basic idea is to return a reference to the object. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <string>

struct Person
{
private:
  std::string _surname;
  std::string _name;
  unsigned    _age;

public:
  Person(): _age(0) { }

  Person& surname( const std::string& s ) { _surname = s; return *this; }
  Person& name   ( const std::string& s ) { _name    = s; return *this; }
  Person& age    ( unsigned           y ) { _age     = y; return *this; }

  std::string surname() const { return _surname; }
  std::string name()    const { return _name;    }
  unsigned    age()     const { return _age;     }
};

std::ostream& operator << ( std::ostream& outs, const Person& p )
{
  return outs << p.surname() << ", " << p.name() << " (" << p.age() << ")";
}

int main()
{
  auto brother = Person().name( "Jason" ).surname( "Schmidt" ).age( 14 );
  auto sister  = Person().age( 15 ).name( "Jane" ).surname( brother.surname() );

  std::cout 
    << brother << " is brother,\n"
    << sister  << " is sister.\n";
}

Here I return proper references, but you can return a pointer instead and then use the indirection operator as in your post.

There is also no requirement that you return a reference to the same object.


The technique is surprisingly rare, if very convenient. I often use it in the construction of my own objects, especially those that pretend they are functions that do something useful.

For example, a fancy sort function might take a gazillion arguments, and if you are lucky, the person who wrote the function made arguments you can read and understand without guessing what a random-looking true appearing in a long argument list means. Conscientious coders will often commentate on what they really mean when calling such a function:
1
2
3
4
5
6
7
8
  super_sort( xs.begin(), xs.end(), 
    ssmDictionary,         // (ignored)
    ssNonDecreasing,       // (normal/default)
    2,                     // (WANT THIS: stride = 2)
    ssmNoCase,             // (ignored)
    ssmUnique,             // (WANT THIS)
    my_sort_compare_method // (WANT THIS)
  );

This has drawbacks, not the least of which is the fact that, just to use my_sort_compare_method() I had to also specify all the other (to be ignored) arguments...

With a function object, you can make life a whole lot friendlier with a simple function-construction-object, which results in calling code like this:

1
2
3
4
  super_sort( xs.begin(), xs.end() )
    .stride( 2 )
    .comparitor( my_sort_compare_method )
    .unique();

This is significantly more readable and maintainable. Do you see why it this construct is called “named parameter”? You never have to guess about an argument. You can list them in any order, and only those that you wish to alter from the default behavior.


There are other contexts where this is useful. A common one is in the construction of an object such as a file stream. It typically employs a separate object to gather the parameters by which the object should be constructed. After construction, none of the parameters can be modified. (For example, the filename cannot be changed once the file is open.)

 
  file f( file::name( "quux.txt" ).readonly().create() );

Again, I prefer to make the temporary parameter object less obvious:

 
  auto f = file( "quux.txt" ).readonly().create();

Both ways are valid and useful.

Hope this helps.

[edit]
Oh, this kind of construct is not necessarily the named parameter idiom. It is just most common in that context. There are plenty of other contexts where it is also valid.

The most common is when you have a function that returns a pointer to an object to operate on. So instead of:

1
2
  Bar* my_bar = my_foo.do_something();
  my_bar->do_something_else();

You can just say:

 
  my_foo.do_something()->do_something_else();


Ok, done now.
Last edited on
got it now, thanks
Hey, if you’re still reading this, I just came across an old article about this, which is also worth reading.
https://martinfowler.com/bliki/FluentInterface.html

[edit] Sorry, posted the wrong link. This is the one you should go to: 
https://softwareengineering.stackexchange.com/questions/219593/why-do-many-languages-not-support-named-parameters

I’ve used this idiom for ages, but only now do I have a name for it. :O)
Last edited on
20 years ago I wrote some code with complex data structures. I made a conscious decision to have everything point "forward and backward" so, for example, if class X has a collection of objects of class Y, then the Y objects point back to the X object. This way I can find data based on any relationship imaginable.

I wanted to be certain that going forward, if someone wanted a new feature, I wouldn't find myself thinking "well shoot, you can't get there from here." It's a glorious thing when you're looking at one piece of data and you need another related piece that's located in the next county and you can just go "hop hop hop" and you're there.

I just looked through the code and found this example:
bucket->addrs.first()->acIdx
There are probably longer ones, but this gives you an idea.
Very tight! Nice!

@programmy
You should notice that dhayden has posted a reason for this kind of construct that I have not mentioned.
Topic archived. No new replies allowed.