Click here to Skip to main content
Click here to Skip to main content
Go to top

General Policy Idiom

, 31 Jan 2007
Rate this:
Please Sign up or sign in to vote.
The article describes an idiom which can help create a highly customizable template library in C++ by applying an immense level of parameterization.

Background

Policy and traits based design is a very powerful and commonly used practice in template meta-programming. But this practice has several serious issues. One is able to manage library classes by applying different policies, but he is unable to manage policies themselves. At first, it is practically impossible to add a new policy to an existing and widely used class. Let's look at the Standard Template Library as an example. It is practically impossible to add threading models to STL containers, because a lot of code supposes that std::vector has only two template parameters - value type and allocation policy. Moreover, if any STL class doesn't use allocation policy yet (such as std::exception), it cannot start using this standard policy. Also, it is very hard to manage dependencies between policies. The easiest way to manage dependencies is to hardcode it and to customize one policy with another. But this way is not a flexible one. A different implementation policies set may require a different set of dependencies.

So now, the situation with policies looks like we don't hardcode implementation library types, and allow one to customize it with policies, but we do hardcode both the list of possible policies (and cannot enlarge this list later) and the dependencies between those types.

Problem overview

An example of the hardcoded dependences between policies can be seen in the Loki library [2] in the SmartPtr class. The declaration of this class is as follows:

template
<
   typename T,
   class OwnershipPolicy,
   class ConversionPolicy,
   class CheckingPolicy,
   class StoragePolicy
>
class SmartPtr
   : public StoragePolicy::In<T>::type
   , public OwnershipPolicy::In<typename StoragePolicy::template PointerType<T>::type>::type
   , public CheckingPolicy::In<typename StoragePolicy::template StoredType<T>::type>::type
   , public ConversionPolicy
{/* ... class body */};

In this example, any implementation of OwnershipPolicy and CheckingPolicy must depend on StoragePolicy. One cannot create such an implementation of StoragePolicy, so it will depend of CheckingPolicy.

As I see it, all those problems with policies happen because we pass all the policies to the class of the library through separate template parameters. Each policy is passed through its parameter, and this parameter may have a default value. There is one more problem with passing default values before non-default ones. So with passing default values, we have no less problems than with policies themselves.

Solution overview

First approach

The obvious and the simplest possible solution of all these problems is to pass all policies and traits to all the classes of the generic library through one template parameter - a General Policy. Indeed, neither the list of necessary policies nor dependencies will be hardcoded in such a case. The library will only organize the structure of the general policy by providing a default_general_policy class. The user will be able to inherit this class and override only some of the policies. Thus, new policies can be added to the general one later. All the classes of the library will receive a general policy template argument with a default value, default_general_policy. So, any default can be changed in default_general_policy, and the user will start using the new defaults automatically. One more benefit of the general policy is the fact that the user of the generic library is able to make sure that all user code uses a single set of policies for all purposes, and that can we easily change this set without reviewing and overwriting the entire code. The user of such a library can change a policy in one place and the entire code will start using this new implementation of the policy.

The simplest example of using this idiom may look like this:

#include <span class="code-keyword"><iostream></span>
#include <span class="code-keyword"><string></span>

namespace provided_library {

   // Library provide default set of policies.

   // Any number of policies can be added here during period

   // of lifetime of generic library.

   class default_general_policy {
   public:

      //Example of outputing policy.

      class output_t {
      public:
         static std::ostream& output_stream() {
            return std::cout;
         };
      }; // class output_t


      class auto_logging_t {
      public:
         auto_logging_t(const std::string& method_name)
            : _method_name(method_name)
         {
            output_t::output_stream() << "Started Method : "
               << _method_name << std::endl;
         }

         ~auto_logging_t()
         {
            output_t::output_stream() << "Finished Method : "
               << _method_name << std::endl;
         }

      private:
         std::string _method_name;
      }; // class auto_logging_t


   }; // class default_general_policy


   // Example of some class in generic library

   template<typename general_policy = default_general_policy>
   class worker {
   public:
      typedef general_policy general_policy_t;
      typedef typename general_policy_t::auto_logging_t auto_logging_t;
      typedef typename general_policy_t::output_t output_t;

      static void do_work() {
         auto_logging_t do_logging("worker::do_work");
         output_t::output_stream() << 
           "Worker is working ..." << std::endl;
      }
   }; // class worker


   // ...

   // not only worker but also all classes of provided_library use one

   // general_policy instead of set of policies.


}; // namespace provided_library


// user redefines some aspects of general policy

class user_general_policy : public  provided_library::default_general_policy {
public:
   class auto_logging_t {
   public:
      auto_logging_t(const std::string& method_name)
         : _method_name(method_name)
      {
         output_t::output_stream() << "User Logging Method : "
            << _method_name << std::endl;
      }

      ~auto_logging_t()
      {
         output_t::output_stream() << "User Logging Method : "
            << _method_name << std::endl;
      }

   private:
      std::string _method_name;
   }; // class auto_logging_t

}; // class user_general_policy


int main() {
   provided_library::worker<>::do_work();
   std::cout << std::endl;
   provided_library::worker<user_general_policy>::do_work();
   return 0;
}

Improved approach

In this example, a class worker is able to use any policy from a general one. The class, default_general_policy, provides not only defaults, but also the structure of the general policy, and the worker will expect a class with the same structure as default_general_policy. The user must inherit default_general_policy to be sure that this structure will always be the same, and override types he needs. Thus, one can add the threading_model_t policy, for example, into default_general_policy in a new version of the library and use it in all classes like the worker. Old user code can be compiled with this new version of the library and all library classes will start using the default threading_model_t. After that, the user will be able to override threading_model_t in one place - the definition of the user_general_policy, and all user code will start using the new threading model.

This example is simple enough, but it still contains a serious problem. If we override output_t in user_general_policy, the default implementation of auto_logging_t will still use the default output_t instead of the overridden one. Such a behavior looks incorrect.

The solution is to make an auto_logging_t sub-policy template class. It will receive the implementation of the whole general policy in a template parameter and will be able to use all the overridden types. And, our fixed example may look like this:

#include <span class="code-keyword"><iostream></span>
#include <span class="code-keyword"><string></span>

namespace provided_library {

   class default_general_policy {
   public:

      class output_t {
      public:
         static std::ostream& output_stream() {
            return std::cout;
         };
      }; // class output_t


      // Note that here auto_logging_t becomes
      // template because in depends from other policies.

      template<typename general_policy>
      class auto_logging_t {
         typedef typename general_policy::output_t overrided_output;
      public:
         auto_logging_t(const std::string& method_name)
            : _method_name(method_name)
         {
            overrided_output::output_stream() << "Started Method : "
               << _method_name << std::endl;
         }

         ~auto_logging_t()
         {
            overrided_output::output_stream() << "Finished Method : "
               << _method_name << std::endl;
         }

      private:
         std::string _method_name;
      }; // class auto_logging_t


   }; // class default_general_policy


   template<typename general_policy = default_general_policy>
   class worker {
   public:
      typedef general_policy general_policy_t;
      typedef typename general_policy_t::template 
        auto_logging_t<general_policy_t> auto_logging_t;
      typedef typename general_policy_t::output_t output_t;

      static void do_work() {
         auto_logging_t do_logging("worker::do_work");
         output_t::output_stream() << 
           "Worker is working ..." << std::endl;
      }

   }; // class worker


}; // namespace provided_library


class user_general_policy : public provided_library::default_general_policy {
public:

   class output_t {
   public:
      static std::ostream& output_stream() {
      std::cout << " non-default part of message. ";
            return std::cout;
         };
   }; // class auto_logging_t


}; // class user_general_policy


int main() {
   provided_library::worker<>::do_work();
   std::cout << std::endl;
   provided_library::worker<user_general_policy>::do_work();
   return 0;
}

One has to note that if the user wants to override such a template sub-policy, this sub-policy must use its template parameter like the default auto_logging_t does in the example, instead of using the user_general_policy class directly, so that we will be able to inherit user_general_policy and override some other policies. Conversely, if we use user_general_policy in the overridden auto_logging_t template, instead of the template parameter, it will be hard to find such a problem because the behavior of user_general_policy will look correct and the problem becomes apparent only after we will inherit user_general_policy.

Policy specialization

In some cases, it is necessary to override one or several policies from user_general_policy to use in some particular place. It won't be rational to create a new combination of redefined policies for such cases. For example, a class might encapsulate the reading of a configuration file and must use short int as the type of character instead of something defined in the user general policy. There can be a large number of such cases in the user code, and it is possible that each case will require a different combination of the same policies.

This situation can be solved with policy specializators. Policy specializator is a template class which receives a general policy in the template parameter and overrides some single aspect of it. The Specializator of the output policy in our example may look like:

template<typename general_policy_t>
class output_specializator : public general_policy_t {
public:

   class output_t {
   public:
      static std::ostream& output_stream() {
         std::cout << " non-default part of message. ";
         return std::cout;
      };
   }; // class auto_logging_t


}; // class output_specializator

typedef provided_library::default_general_policy user_general_policy;
// ...

provided_library::worker< output_specializator<user_general_policy> >::do_work();

Also, the library can provide default values of each policy as specializators and use a single typedef to combine them all into one general policy.

Parameterizable library

As far as possible, all classes of a generic library are parameterized with a single general policy template parameter. It will be only rational to make a whole library as a single class parameterized with a general policy. It is not necessary to define an entire library; such a class can be injected there by the following method:

template<typename general_policy>
class some_simple_class {/* ... body of the simple class ... */};

// template class has to have template_parameter after
// it will be parametrized with general_policy

template<typename general_policy, typename template_parameter>
class some_template_class {/* ... body of the template class ... */};

template<typename general_policy = default_general_policy>
class provided_library { // provided_library is class now

public:
   typedef general_policy general_policy_t;
   typedef ::some_simple_class<general_policy_t> some_simple_class;

   // It is impossible to make alias name (typedef) for template,

   // so it must be wrapped in a template class instead.

   template<typename template_parameter>
   class some_template_class {
   public:
      typedef ::some_template_class<general_policy_t, 
              template_parameter> result;
   }; // class some_template_class


}; // class provided_library

// ... Ussage:

typedef provided_library<user_general_policy> customized_library;
// ...

customized_library::some_simple_class simple_worker_object;
typedef customized_library::some_template_class<int> customized_template_worker;
typename customized_template_worker::result template_worker_object;

References

  1. C++ Templates: The Complete Guide, by David Vandevoorde, Nicolai M. Josuttis.
  2. Modern C++ Design: Generic Programming and Design Patterns Applied, by Andrei Alexandrescu.

History

  • 1 February 2007 - Initial revision.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Volodymyr Frolov
Architect
Ukraine Ukraine
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 31 Jan 2007
Article Copyright 2007 by Volodymyr Frolov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid