Static Polymorphic Named Parameters in C++

Static Polymorphic Named Parameters in C++

By Martin Moene

Overload, 22(119):4-6, February 2014


Adding parameters to an object can be messy. Martin Moene demonstrates how method chaining can make code more readable.

For a new kind of measurement in our application for scanning probe microscopy [ Wikipedia-a ], I need to construct a curve that consists of several kinds of segments. The curve can for example describe the movement of the tip perpendicular to the surface of the material investigated and what data shall be acquired.

Behaviour of segment types varies. One segment may describe how the surface is approached, another how to move away from the surface and yet another describes a dwell time. Such a curve is part of force-distance spectroscopy [ Wikipedia-b ]. Figure 1 below shows what the researchers would like to do.

Figure 1

To a large extent the structure of a curve is fixed and this structure can be created at compile-time. Some variation is required at run-time, which can be arranged for via parameters. The simplified code in Listing 1 illustrates this.

#include "curve.hpp"

int main()
{
  // run-time configurable:
  const int  Nsweep    = 1;
  const bool skipR1    = false;
        auto scanner   = create_scanner  ( "Z" );
  const auto distance  = create_condition( "123 nm" );
  const auto threshold = create_condition( "chan1", "<=", "2.7 V" );

  Curve curve;

  curve.times( Nsweep  )
    .scans( scanner )
    .add  ( Retract ().stop_on( distance  ) ).unless( skipR1 )
    .add  ( Approach().stop_on( threshold ) )
    .add  ( Retract ().stop_on( distance  ) )
    ;
  std::cout << "curve.sweep(): "; curve.sweep();
}
			
Listing 1

The curve built describes that: Nsweep times, scanner Z retracts the tip from the material surface for 123 nm (unless skipped), then approaches the surface until the measured value of chan1 reaches the threshold value of 2.7 V, and finishes with retracting 123 nm again (t1 and t2 are omitted). Note that the threshold condition depends on other information than the scan distance. It can have any unit that makes sense in the experiment.

In the real implementation, there are many more parameters. To keep it simple, several things such as data acquisition are omitted here. Another simplification is to use struct without access specifiers for all classes in the code.

Compiling and running the simplified program gives:

prompt>g++ -Wall -Wextra -Weffc++ -std=c++11 \
-o curve.exe curve.cpp && curve
curve.sweep(): RZL AZ<= RZL

The output RZL AZ<= RZL indicates the type of segment, scanner and condition used for each segment: R for Retract, A for Approach, Z for ZAxisScanner, <= for LessEqualCondition and L for LengthCondition.

Fluent interface

The construct sketched above evolved from the following (simplified) C++98 code (Visual C++ 6).

return CurveDefinition().
  sweep( sweepCount ).
  add( CurveSegmentPtr( new SweepCurveSegment
    ( scanner, condFalse, ... ) ) ).
  add( CurveSegmentPtr( new SweepCurveSegment
    ( scanner, condition, ... ) ) ).
  add( CurveSegmentPtr( new SweepCurveSegment
    ( scanner, condFalse, ... ) ) );

Although the sketched curve may be adequate for many kinds of experiments, it is a simplification of a more general approach. Experience with an initial version of the code led the researchers to express several additional wishes. For example to be able to conditionally include a segment, to only perform it once, or to perform a collection of segments multiple times.

As you see, the new code expands on the use of method chaining [ Wikipedia-d ], a key element of a fluent interface [ Wikipedia-e ]. Method chaining is also known as the named parameter idiom. In addition to method chaining, other variations are imaginable, such as function-like modifiers. For example to include a segment in the first sweep only with once( segment ) or to perform a collection of segments (a sub-curve or section) a number of times via times( N, section ) .

The new code also moves the allocation of segments out of the fluent interface. This makes the code much more readable. It also leads to the main subject of this article: static polymorphic named parameters.

Thus, the reason to compose the curve in this way is to benefit from a clear and flexible notation. As an internal domain-specific language [ Fowler08 ] it also helps researchers to recognise the curve they sketched in the code.

Static polymorphism

Now, let’s examine how a curve is constructed. Curve’s method add() creates dynamic segment objects from the non-dynamic temporary ‘exemplars’. To create a dynamic copy of the segment of the original type, add() is templated.

  template< typename T >
  Curve & Curve::add( T const & segment )
  {
    segments.emplace_back(
      std::make_unique<T>( segment ) );
    return *this;
  }

Note: std::make_unique<T>() is a C++14 feature [ make_unique ].

Looking at above code, it becomes clear that a call like Approach().stop_on(...) must itself return an object of type Approach to add the right type of segment to the curve.

Here is the crux of this article. Do all types such as Approach require their own method stop_on() to return the appropriate type? Fortunately that’s not the case, thanks to the curiously recurring template pattern or CRTP. See [ Wikipedia-e: Subclasses ] and [ Wikipedia-f ] respectively. (See Listing 2.)

template <typename Derived>
struct SegmentParameter : SegmentCommon
{
#define self crtp_cast<Derived>(*this)

    Derived & stop_on( ConditionPtr cond )
    {
        condition( cond );
        return self;
    }

#undef self
};

struct Approach : SegmentParameter<Approach>
{
   // inherited: 
   // Approach & stop_on( ConditionPtr s );
};
			
Listing 2

With this construct Approach can inherit stop_on() that returns the desired Approach & instead of SegmentParameter & . The crtp_cast combined with a macro enables us to write return self to return the current object with the right type where we would otherwise write return *this [ Bendersky11 ]. The shortest of four crtp_cast const-volatile variations is:

  template<class D, class B>
  D & crtp_cast(B & p)
  { return static_cast<D &>( p ); }

For an interesting discussion about encapsulation and the CRTP, see Better Encapsulation for the Curiously Recurring Template Pattern by Alexander Nasonov [ Nasonov05 ].

Build to use

In the end we’ve built a curve that contains a collection of smart-pointered segments that originate in interface Segment . At the same time the curve is a segment itself, so that it can act as a sub-curve or section of another curve. See the following derivation chains.

  Curve → SegmentParameter<Curve> → SegmentCommon →  Segment
  Approach → SegmentParameter<Approach> → SegmentCommon → Segment
  ...

Thus, whereas construction of the curve builds on automatic ‘exemplar’ objects and static polymorphism via the CRTP compile-time technique, using the curve occurs via classical dynamic polymorphism with Segment as the interface.

Letting go of the garbage

One little thing worries me: the code for the sequence curve.add(...).unless(...) is both elegant and inelegant at the same time. It is simple, but then it lets you create a segment to only throw it away immediately via unless() .

  Curve & unless( bool skip )
  {
    if ( skip )
      segments.pop_back();
    return *this;
  }

One can argue that recycling is good and more garbage means more recycling, but that isn't entirely in line with Bjarne Stroustrup’s idea [ Kalev13 ]:

So, I say that C++ is my favorite GC language because it generates so little garbage.

As a reviewer pointed out, one may circumvent the awkward situation by prefixing the condition, or by including it in a conditional add function like so:

  curve.enable_if_not( skipR1 )
    .add( Retract ().stop_on( distance ) );
  curve.add_if_set( performR1,
    Retract ().stop_on( distance ) );

However, to ease reading of consecutive lines, I’d prefer to keep the left part of the lines similar, as illustrated here.

  curve.add_if( Retract ().stop_on( distance  ), performR1 )
       .add   ( Approach().stop_on( threshold ) );

We’re leaving the realms of fluid interfaces and named parameters though.

Summary

In this article a fluent interface is applied to a simplified setting for scanning probe spectroscopy. The resulting code shows a close relationship to the graphic representation provided by the researchers. The main point of the article is to show how one can obtain the static inheritance required for named parameters in this setting via the curiously recurring template pattern.

Acknowledgements

I’d like to thank the Overload team for reviewing the article and Jonathan Wakely for clarifying several aspects of smartpointers. Their remarks and suggestions were key in improving the article.

Notes and references

Code for this article and code for a larger example with modifiers once and times is available on [ GitHub ].

[Arena12] Use CRTP for polymorphic chaining. Marco Arena. 29 April 2012. http://marcoarena.wordpress.com/2012/04/29/use-crtp-for-polymorphic-chaining/ (Presents a slightly different application of the CRTP.)

[Bendersky11] The Curiously Recurring Template Pattern in C++. Eli Bendersky. 17 May 2011. http://eli.thegreenplace.net/2011/05/17/the-curiously-recurring-template-pattern-in-c/ (Mentions crtp_cast in a comment.)

[Fowler05] Fluent Interface. Martin Fowler. 20 December 2005. http://www.martinfowler.com/bliki/FluentInterface.html

[Fowler08] Domain-Specific Language. Martin Fowler. 15 May 2008. http://martinfowler.com/bliki/DomainSpecificLanguage.html

[GitHub] Code for Static polymorphic named parameters in C++. Martin Moene. 31 December 2013. https://github.com/martinmoene/martin-moene.blogspot.com/tree/master/Static polymorphic named parameters in C++

[K-ballo13] Episode Eight: The Curious Case of the Recurring Template Pattern. Tales of C++ K-ballo . 2 December 2013. http://talesofcpp.fusionfenix.com/post-12/episode-eight-the-curious-case-of-the-recurring-template-pattern

[Kalev13] An Interview with Bjarne Stroustrup. Danny Kalev and Bjarne Stroustrup. May 15, 2013. http://www.informit.com/articles/article.aspx?p=2080042

[make_unique] make_unique. CppReference . http://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique Here, function make_unique<>() is equivalent to std::unique_ptr<T>(new T(std::forward<Args>(args)...)) . See also Herb Sutter’s GotW #89 Solution: Smart Pointers ( http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/ ) and GotW #102: Exception-Safe Function Calls ( http://herbsutter.com/gotw/_102/ ).

[Nasonov05] Better encapsulation for the curiously recurring template pattern. Alexander Nasonov. Overload , 70:11-13, December 2005 ( http://accu.org/index.php/journals/296 ).

[ Wikipedia-a] Scanning Probe Microscopy. Wikipedia . http://en.wikipedia.org/wiki/Scanning_probe_microscopy Accessed 21 December 2013.

[Wikipedia-b] Force-Distance spectroscopy . Wikipedia . http://en.wikipedia.org/wiki/Atomic_force_microscopy#Force_spectroscopy Accessed 19 December 2013. See also [ Wikipedia-c ].

[Wikipedia-c] Scanning tunneling spectroscopy. Wikipedia . http://en.wikipedia.org/wiki/Scanning_tunneling_spectroscopy Accessed 19 December 2013.

[Wikipedia-d] Method chaining or named parameter idiom. Wikipedia . http://en.wikipedia.org/wiki/Method_chaining Accessed 16 December 2013. See also [Wikipedia-e].

[Wikipedia-e] Fluent interface. Wikipedia . http://en.wikipedia.org/wiki/Fluent_interface Accessed 21 December 2013. See also [ Fowler05 ]

[Wikipedia-f] Curiously recurring template pattern (CRTP). Wikipedia . http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern Accessed 17 December 2013. See also [ K-ballo13 ], [ Bendersky11 ], [ Arena12 ].






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.