Click here to Skip to main content
15,894,362 members
Articles / Programming Languages / C++

General Policy Idiom

Rate me:
Please Sign up or sign in to vote.
4.79/5 (8 votes)
22 Jul 2020CPOL6 min read 21.9K   16  
Idiom to create customizable template libraries in C++
The article describes an idiom that can be used to create highly customizable template libraries in C++ by applying an immense level of parameterization.

Motivation

Policy- and trait-based design is a very powerful and commonly used practice in C++ template meta-programming, but in its current state, it has several critical flaws.

Even though it is easy to customize library classes by applying different policies and traits to them, it is quite hard to customize the policies and traits 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 (STL) as an example. It is practically impossible to add a multithreading policy to any STL containers because a lot of existing code supposes that std::vector has only two template parameters - value type and allocation policy. Moreover, if any STL-based code doesn't use allocation policy yet (such as std::exception), it cannot start using the new multithreading policy still relying on the STL-defined default allocation policy and without hardcoding it.

Also, it is very hard to manage dependencies between policies (one policy of the parametrized class may need access to the other policies used to customize the target class). The easiest way to manage dependencies is to just hardcode them on every use and to customize one policy with another, which might not be a flexible approach. A different implementation of a set of policies may require a different set of dependencies among the policies.

So, we don't hardcode types when implementing a template library, and we allow customizing such implementation with policies, but we do hardcode both the list of possible policies (and cannot add new policies to this list later) and the allowable dependencies between implementations of such policies.

The goal of this article is to address these flaws and make it easier to utilize C++ template meta-programming when architecting C++ template libraries.

The Problem Example

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

C++
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. It is not possible to create an implementation of StoragePolicy, which depends on CheckingPolicy, for example.

Those problems occur because we pass all the policies to the target class through separate template parameters. Each policy is passed through a dedicated parameter that may or may not have its default value. This creates an additional problem since we need to pass all the parameters without default values before we pass all the parameters with the default values.

Solution Overview

Initial Approach

The naïve attempt to approach this problem is to pass all policies and traits to all the classes of a generic library through a single template parameter — a General Policy. Neither the list of designated policies nor the dependencies between sub-policies are hardcoded in such a case. The template library only organizes the structure of the general policy by providing a default_general_policy class. The user is able to inherit this class and override just the policies of interest. Thus, new policies with their defaults can be added at any point later by the template library maintainer. All the classes of the template library will receive just one general policy template argument with a default value, default_general_policy. Any defaults can be changed in default_general_policy preserving the backward compatibility of the library, and the users will start using the new defaults automatically.

One more benefit of such general policy is the fact that the user of the generic library is able to make sure that all code uses the same set of policies for all purposes, and that the set policies can be changed in one place without revising the entire codebase.

The simplest example of using the General Policy idiom may look like this:

C++
#include <iostream>
#include <string>

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 the example above, the worker class is able to use any sub-policy from general_policy. The default_general_policy class provides not only defaults but also the structure of the general policy. The worker class expects a template parameter with the same structure as default_general_policy. The user must inherit default_general_policy to make sure that the policy structure remains the same, but override user-defined policy types.

In the new version of the library, the library provider can add threading_model_t policy into default_general_policy and use it in all classes like the worker. The backward compatibility is preserved and the old user code can be compiled with the new version of the library. All library classes will use the default threading_model_t. The user is still able to override threading_model_t in one place - the definition of the user_general_policy class. All user's code will start using the new threading model as soon as it is overwritten in user_general_policy.

There's still one flaw left in this example. 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 user-defined one. Such behavior is inconsistent with what we want to achieve.

The solution is to make auto_logging_t a template class accepting general policy as a parameter. It will receive the implementation of the entire general policy in its template parameter and will be able to use all the user-defined types without specifying which exact policies the implementation must rely on.

The proper implementation looks like this:

C++
#include <iostream>
#include <string>

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;
}

In order to make every individual policy overridable, customizable policies must use its template parameter for accessing the general policy, similarly to the default implementation of auto_logging_t in the example above. Such customizable policies must not use the user_general_policy class directly, so that users can inherit user_general_policy and override just some of its policies having an effect on all other policies. Conversely, if we use user_general_policy in the overridden auto_logging_t class, instead of the template parameter, it will be hard to find the problem because the user_general_policy class will behave correctly until somebody inherits it.

Policy Specialization

In some cases, it is necessary to override one or several policies from user_general_policy in order to use it in some particular place. It would be cumbersome and error-prone to create a new combination of redefined policies for all such cases. For example, a class might encapsulate the reading of a configuration file and must use short int as the type of characters instead of char defined in the user general policy. There can be a large number of such cases in the user code base, and it is possible that each case will require a different combination of the policies to be redefined.

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 a single aspect of it. The specializator of the output policy in our example above may look like this:

C++
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

All classes of a General Policy-based library must be parameterized with a single general policy template parameter. It could be convenient to implement the whole library as a single class parameterized with the general policy. It is not necessary in this case to define the entire library in one file though; such a class can be constructed by adding sub-classes there by using of typedef:

C++
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. David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide, 2002.
  2. Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied, 2001.

History

  • 1st February, 2007 - Initial revision
  • 22nd July, 2020 - Grammar corrections

License

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


Written By
Architect
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --