Showing posts with label templates. Show all posts
Showing posts with label templates. Show all posts

Sunday, May 3, 2009

Memberwise Visitation

This code was the original impetus for my postings on template template parameters and multi-paradigm programming, but those didactic digressions grew into articles of their own. I don't know how helpful such digressions are - I'm learning that truly effective teaching requires the patience to develop good examples and an awareness of where your target audience is, and I'm not sure that I possess either, at least in this format - but at least they're out of the way...

C++'s object-oriented and generic programming each offer a lot of capability, but they can be combined for even more power. Consider, for example, the following code:

#include <iostream>
#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/shared_ptr.hpp>

template <typename Class>
class ClassVisitor {
public:

  class MemberVisitor {
  public:
    virtual ~MemberVisitor() {}
    virtual void Visit(Class& instance) = 0;
  };

  template <typename T>
  class MemberVisitorImpl : public MemberVisitor {
  public:
    MemberVisitorImpl(T Class::*ptr) : mMember(ptr) {}
    void Visit(Class& instance) {
      // Sample visitor method.  You can define whatever member-specific
      // behavior you want.
      std::cout << instance.*mMember << ' ';
    }
  private:
    T Class::*mMember;
  };

  template <typename T>
  ClassVisitor& Declare(T Class::*ptr);

  void Visit(Class& instance);

private:
  typedef boost::ptr_vector<MemberVisitor> MemberVisitorList;
  MemberVisitorList mMemberVisitors;
};

template <typename Class>
template <typename T>
ClassVisitor<Class>& ClassVisitor<Class>::Declare(T Class::*ptr)
{
  mMemberVisitors.push_back(new MemberVisitorImpl<T>(ptr));
  return *this; // supports a fluent interface
}

template <typename Class>
void ClassVisitor<Class>::Visit(Class& instance)
{
  for (typename MemberVisitorList::iterator i = mMemberVisitors.begin();
       i != mMemberVisitors.end();
       i++) {
    i->Visit(instance);
  }
}

struct Annotation {
  const char *mMessage;
  int mX;
  int mY;
};

int main(int argc, char *argv[])
{
  ClassVisitor<Annotation> def;
  def.Declare(&Annotation::Message)
    .Declare(&Annotation::X)
 .Declare(&Annotation::Y);

  Annotation annotation;
  annotation.Message = "Note this";
  annotation.X = 10;
  annotation.Y = 20;
  def.Visit(annotation);
  return 0;
}

This code offers one approach to visiting each member of a class with a client-specified operation. Generic programming is used MemberVisitor subclasses handle members of any type, while OOP's polymorphism is used to let different types be handled at runtime. (Normally templates can only be applied at compile-time.) The example code simply dumps member variables' contents to screen and ends up being merely an overcomplicated operator<<(std::ostream&, const Annotation&), but MemberVisitor instances can contain additional information about their member variables and can define more than one Visit operation. In my current code's memberwise visitor implementation:

  • MemberVisitor instances contain member variables' descriptions, defaults, and constraints (minimum, maximum, etc.).
  • Copy constructors, operator=, and default constructors can be implemented by iterating over the list of member variables and default values that the MemberVisitor list provides.
  • MemberVisitors' information is shared by visit operations: per-member serialization and unserialization use members' types and formats, constraints are used during unserialization to check for corrupt archives and at runtime to check design by contract-style invariants, member descriptions and defaults can be used to report errors and reset to a known good state if a corrupt archive is encountered during deserialization, and the class can easily be dumped one member at a time to a human-readable format for inspection and debugging.
  • A second set of visitors handles binding members to a form's fields: loading form fields from a class instance, checking whether a form is dirty, validating a form, and saving form fields to a class instance are each implemented as Visit operations, and information such as MemberVisitor constraints can be used when a form is loaded to configure the UI widgets and before a form is saved to check for validity.

The result is that the MemberVisitors offer some introspection/reflection capability for C++ as well as something like a database schema for each class, similar to what would be provided by most frameworks' database support, but not tied in to a database backend. This multiparadigm (OO-and-generic) combination is only one approach to implementing this functionality; other implementations could use more of a pure template metaprogramming approach or could rely on preprocessor macros, code generation via an external tool, or reflection (via vendor extensions or a third-party library). Each implementation offers its own tradeoffs for complexity, portability (template metaprogramming often runs into compiler bugs; reflection is often vendor-specific), and build system support (in the case of code generation and reflection).

I'm still playing around with different approaches trying to find the best balance of tradeoffs and trying to figure out the best way to take advantage of C++'s multiparadigm support without overcomplicating my code. Are there other examples of code that directly leverages C++'s multiparadigm support? Are there preexisting implementations of this kind of member schema functionality for C++? Would there be any interest in a (cleaned up and open-sourced) member schema library, or is that functionality adequately covered by database libraries? Comments welcome.

Tuesday, April 7, 2009

Things I Didn't Know about C++: Template Template Parameters

Continuing my series on things I didn't know about C++...

I knew about templates, and I knew about parameters to templates, but I did not know about template template parameters, which are useful when dealing with containers and policies and, if nothing else, are worth learning about for having such a "pleasingly repetitious name" (in C++ author Stephen Dewhurst's words).

A template template parameter is simply a template parameter that is itself a template. Dewhurst gives the example of a Stack template that can be customized to use a particular underlying storage container:

template <typename T, template <typename> class Cont>
class Stack;

Stack<int, List> stack_of_ints;

Without template template parameters, the base type (int in this case), which hampers readability at best and could be a source of errors at worst:

template <typename T, typename Cont>
class Stack;

Stack<int, List<int> > stack_of_ints;
Stack<double, List<int> > stack_of_doubles_badly_truncated_to_ints;

Template template parameters can be used anywhere you need to let the caller customize an algorithm or class for a particular container, policy, or similar:

  • A stack or other higher-level container can be instantiated using any lower-level container, as in Dewhurst's example.
  • A class or function can be customized with a policy template. Andrei Alexandrescu gives an example of this in section 1.5 of Modern C++ Design. (His example is also summarized in this Stack Overflow post.)
  • Template template parameters can be used when you need to know both the type of a template and how that template was instantiated, although this comes up less often than you might initially think, because there are often better ways to find the types involved. For example, there's no need to declare template <typename T, template <typename> class Cont> void ProcessContainedObjects(Cont<T>& cont) to let ProcessContainedObjects know what T is when any well-written container will define the Cont::value_type typedef.

Template template parameters are a useful addition to the C++ template programming toolbox, alongside techniques such as partial template specialization, type traits, concepts (also in C++0x), and C++0x's variadic templates (which can be simulated in C++03 to some extent by using default template arguments or by using Boost Preprocessor to create overloads of every arity from 1 through a #define'd limit).