Niall’s virtual diary archives – Friday 15th May 2015

by . Last updated .

Friday 15th May 2015: 4.23pm. Link shared: https://en.wikipedia.org/wiki/Policy-based_design

As part of publicising my C++ Now 2015 talk this week, here is part 14 of 20 from its accompanying Handbook of Examples of Best Practice for C++ 11/14 (Boost) libraries:

14. DESIGN: Consider making (more) use of ADL C++ namespace composure as a design pattern

Most C++ programmers are aware of C++ template policy based design. This example is taken from  https://en.wikipedia.org/wiki/Policy-based_design:

#include <iostream>
#include <string>
 
template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy
{
    using OutputPolicy::print;
    using LanguagePolicy::message;
 
public:
    // Behaviour method
    void run() const
    {
        // Two policy methods
        print(message());
    }
};
 
class OutputPolicyWriteToCout
{
protected:
    template<typename MessageType>
    void print(MessageType const &message) const
    {
        std::cout << message << std::endl;
    }
};
 
class LanguagePolicyEnglish
{
protected:
    std::string message() const
    {
        return "Hello, World!";
    }
};
 
class LanguagePolicyGerman
{
protected:
    std::string message() const
    {
        return "Hallo Welt!";
    }
};
 
int main()
{
    /* Example 1 */
    typedef HelloWorld<OutputPolicyWriteToCout, LanguagePolicyEnglish> HelloWorldEnglish;
 
    HelloWorldEnglish hello_world;
    hello_world.run(); // prints "Hello, World!"
 
    /* Example 2 
     * Does the same, but uses another language policy */
    typedef HelloWorld<OutputPolicyWriteToCout, LanguagePolicyGerman> HelloWorldGerman;
 
    HelloWorldGerman hello_world2;
    hello_world2.run(); // prints "Hallo Welt!"
}

This works very well when (a) your policy implementations fit nicely into template types and (b) the number of policy taking template types is reasonably low (otherwise you'll be doing a lot of typing as any changes to the policy design requires modifying every single instantiation of the policy taking template types). Another problem with policy based design is that it generates a lot of template instantiations, and generating a lot of template instantiations is bad because compilers often cannot do constant time type lookups and instead have linear or worse type lookups, so instantiating tens of million types is always going to be a lot slower to compile and sometimes link than millions of types.

Consider instead doing an ADL based namespace composure design pattern which is just a different way of doing policy based design. It can be highly effective in those niches where the traditional policy taking template approach falls down. Here is the same program above written using ADL namespace composure:

#include <iostream>
#include <string>

template<typename MessageType>
void print(MessageType const &message)
{
  std::cout << message << std::endl;
}
namespace HelloWorld
{
  template<class T> void run(T v)
  {
    print(message(v));  // Cannot instantiate message() nor print() until T is known
  }
}
namespace LanguagePolicyEnglish
{
  struct tag {};
  template<class T> std::string message(T)
  {
    return "Hello, World!";
  }
}
namespace LanguagePolicyGerman
{
  struct tag {};
  template<class T> std::string message(T)
  {
    return "Hallo Welt!";
  }
}
namespace LanguagePolicyDefault
{
  struct tag {};
  using LanguagePolicyGerman::message;
}
int main()
{
  /* Example 1 */
  {
    using namespace LanguagePolicyEnglish;
    using namespace HelloWorld;
    run(tag()); // prints "Hello, World!"
    // This works because HelloWorld::run()'s message() resolves inside these
    // braces to LanguagePolicyEnglish::message() to the same namespace as
    // struct tag thanks to argument dependent lookup
  }

  /* Example 2
  * Does the same, but uses another language policy */
  {
    using namespace LanguagePolicyGerman;
    using namespace HelloWorld;
    run(tag()); // prints "Hallo Welt!"
    // Whereas HelloWorld::run()'s message() now resolves inside these
    // braces to LanguagePolicyGerman::message()
  }

  /* Example 3 */
  {
    using namespace LanguagePolicyDefault;
    using namespace HelloWorld;
    run(tag()); // prints "Hallo Welt!"
    // Tries to find message() inside namespace LanguagePolicyDefault,
    // which finds message aliased to LanguagePolicyGerman::message()
  }
}

The first example instantiates five types to be thence considered during global type lookup, so let's say it has cost five to future code lookups. The second example instantiates no types at all at global lookup scope, so it has cost zero to future code lookups because all the types are declared inside namespaces normally not considered during global type lookups. The second example may also require less refactoring in the face of changes than the traditional form.

The above pattern is in fact entirely C++ 03 code and uses no C++ 11. However, template aliasing in C++ 11 makes the above pattern much more flexible. Have a look at  https://github.com/ptal/expected/blob/master/include/boost/functional/monads/rebindable.hpp for examples of this ADL invoked namespace composure design pattern.

Presentation slides: https://goo.gl/VHrXrj

#cpp  #cplusplus #cppnow   #cppnow2015   #c++ #boostcpp   #c++11 #c++14

Go back to the archive index Go back to the latest entries

Contact the webmaster: Niall Douglas @ webmaster2<at symbol>nedprod.com (Last updated: 2015-05-15 16:23:25 +0000 UTC)