Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Automatic Static C++ Counter with Scope

4.88/5 (16 votes)
14 Jan 2015CPOL8 min read 32.8K   649  
A small self-contained header file implementation of an static counter for use at compile-time.

Introduction

This is a small self-contained header file implementation of a static counter for use at compile-time. The features of this implementation are:

  • More portable than the <a href="http://msdn.microsoft.com/en-us/library/b0084kay.aspx">__COUNTER__</a>[^] non-standard compiler MACRO.
  • Multiple instances are possible in a single compilation unit, unlike __COUNTER__.
  • Uses a natural syntax, unlike the <a href="http://www.boost.org/doc/libs/1_55_0/libs/preprocessor/doc/index.html">Boost::Preprocessor</a>[^] counter.

Currently this implementation is only capable of use on a small scale, within 50-100 lines for each counter instance. It was designed to be embedded within another MACRO, where each time the outer MACRO is called, the counter increments. This utility would be useful for creating unique registration ids, or automated index entries.

Background

Recently I have been developing a network communication library, Alchemy[^]. Alchemy automatically manages byte-order conversion of data for network transfer. It is capable of programmatically inspecting message definitions and performing the proper conversions. This task is performed by iterating over an array of types that define the message. I iterate over the type list starting with a zero-based index.

I saw an example in Advanced C++ Metaprogramming by, Davide Di Gennaro, of a template meta-program that used the __LINE__ preprocessor MACRO to define unique identifiers. His example showed a template that used indexing starting from zero and counting up to return the line-number the template was defined on as a unique ID. I thought if I could build the reverse template, that would be one less field for users to defined. I first turned to the Internet, and I could not find a solution to this problem. So I set out to find a solution, and that is what I present to you here.

Using the Code

It is a set of MACROs that must be unique to a particular scope. They can be used in a class definition or namespace. Unfortunately, it's not possible to instantiate this version of the counter inside of a function. Even from inside of a nested class. Sorry, Demz da rulz. Here is a quick demonstration of what using the counter looks like.

C++
// Basic use declaration.
DECLARE_COUNTER            // Line: Base
INC_COUNTER                // Line: Base + 1
INC_COUNTER                // Line: Base + 2

INC_COUNTER                // Line: Base + 4




INC_COUNTER                // Line: Base + 8

INC_COUNTER                // Line: Base + 10

The previous block creates a set of templates that will return the counted index declared on a line, when passed the line number - base number. The block of code below demonstrates how the values are extracted from their declarations.

C++
cout << "line(1)  value: " << auto_index<1>::value  << "\n"
     << "line(2)  value: " << auto_index<2>::value  << "\n"
     << "line(4)  value: " << auto_index<4>::value  << "\n"
     << "line(8)  value: " << auto_index<8>::value  << "\n"
     << "line(10) value: " << auto_index<10>::value << "\n";

// The output is:
//
// line(1)  value: 0
// line(2)  value: 1
// line(4)  value: 2
// line(8)  value: 3
// line(10) value: 4
//

This contrived example is intended to demonstrate the separate components of the utility. First there is the counter definition, second the auto_index template that is used to query the index. The previous block would be difficult to use correctly in any non-trivial program. That is why this counter is intended to be embedded within a user-defined MACRO. This will allow the increment call, and the index access to occur on the same line.

The block below demonstrates an example closer to how the static counter was intended to be used:

C++
// Sample Usage MACRO
#define REGISTER_OBJECT(CLASS)                                    \
  INC_COUNTER                                                     \
  static const size_t CLASS::k_uid = auto_index<__LINE__ - k_base>::value;

// Each type of class to be registered needs a unique ID, 
// There is an internal constant named k_uid that holds this id.
DECLARE_COUNTER

REGISTER_OBJECT(ObjectTypeA);
REGISTER_OBJECT(ObjectTypeB);
REGISTER_OBJECT(ObjectTypeC);
REGISTER_OBJECT(ObjectTypeD);

After the preprocessor runs, the use of the __LINE__ compiler MACRO will appear on the same line as the declaration of INC_COUNTER. When the base enumeration line number is subtracted from this instantiation line number, a new value will be defined with the next index value, starting from zero.

Points of Interest

The first thing that I would like to mention, is that I was originally attempting to create a set of template only definitions for the implementation. However, there is one thing that I was just not able to find a solution around, template specialization. Template specialization has a restriction, that it must be performed within the namespace or a parent namespace, where the template definition resides.

When I reached the solution that was mostly working, I tried to apply the counter towards the purpose for which I created the counter to begin with. I quickly realized it was not going to work because my use required the counters to occur in a class scope. I then spent a few days away from this project.

When I returned, I realized that if I converted these templates to a MACRO, I could redefine a set of these templates within whatever scope that I wanted to use the counter. This actually is a better solution, because multiple counters can be used in a single compilation unit. They simply need to be placed in their own scope.

How does it work?

In principle, the concept is very simple. For each instance of the auto_index template that is defined:

  • When a new instance of the counter is created, instantiate a template marking that line as an index entry.
  • For the declaration of the new line, start one line back and search until the most recently created index is found.
  • The base line and below that will be the lower limit and act as the terminating case for counting backwards.

Unfortunately, this turned out to be one of those problems where the devil is in the details. It is important to remember that once a declaration is started for a single counter instance, the entire definition with the instantiation must be completed with the declaration of a single statement.

Why?

Because the goal of this solution is to have a single MACRO, which is called the exact same way every time it is instantiated. Yet it must still increment a value. Here are the other challenges that I had to consider or overcome to reach a solution:

  • The solution must resolve statically at compile-time (For the originally intended use).
  • No state could be created. Once a definition was complete and the compiler moved to the next statement, the previous state is not accessible.
  • Declaring variables or typedefs would be a challenge because the symbol names would clash within the same scope.
  • Schrodinger's Template

Quantum Template Programming

What the hell is a Quantum Template? This one had me tied in knots attempting to work through a solution. Fairly early I had arrived at the counting backwards solution design. I also decided that I would mark the valid lines with the enum_entry template, defined with the true_type. That would leave lines that did not have a definition to return false_type when enum_entry was queried to see if the current line defines an index.

This is when I saw Schrodinger's Cat in template form. I am trying to discover if a template definition was valid or not. However, the moment that I query for that template, I have just defined it. If I had just left it alone, it wouldn't have existed. This is where template specialization became necessary.

I was shooting for a solution where I simply defined a template for lines that were to increment the counter. However, there is simply no way to generically define a valid line as well as the invalid line. In order to break this maddening cycle, I had to turn to specialization.

Here is the default template definition. This declaration marks the entry as not a valid enumeration by default.

C++
template<int N, bool IsValidT = false>
struct enum_entry
  : std::integral_constant<bool, IsValidT>
{ };

This is the specialized template that is defined for each line that increments the counter.

C++
template < >
struct enum_entry< (ID) >
  : std::true_type
{ };

The Work

After the enumeration entry specialization has been created, the auto_index < int L < must be called in order for the counter value to be assigned in increasing order just as the lines are defined. This sets the recursive template processing in motion.

C++
template <int L>
struct auto_index
  : std::integral_constant< int, index_before<L>::value + 1 >
{ };

The template is defined to create a simple static constant, where the current value of the counter will be set to the last value plus one.

C++
std::integral_constant< int, index_before<L>::value + 1 >

index_before is a convenience template to simplify the first request for the previous index. This sets the search in motion to start at the previous line, and count back by one, until the last valid enum_entry template has been found.

C++
template <int L>
struct decrement_until_match
  : std::integral_constant< int,
                            value_if< enum_entry<L>::value,
                                      int,
                                      auto_index<L>::value,
                                      decrement_until_match<L - 1>::value
                                    >::value
                          >
{ };

Let's zoom in and take a close look at the decision point.

C++
value_if< enum_entry<L>::value,                // Predicate
          int,
          auto_index<L>::value,                // Previous Index Found
          decrement_until_match<L - 1>::value  // This is not a valid line,
        >::value                               // search the previous line.

value_if is a conditional template that I wrote to simplify the syntax required when making a Boolean decision, then assigning a value based on the result TRUE | FALSE . Its behavior is the moral equivalent of the ternary operator ?: for meta-programming. The implementation is straight-forward. There are two definitions of the template; one for the true case and one for the false case.

C++
//  ***************************************************************************
/// This construct provides the compile-time equivalent of
/// the ternary operator ?: for value selection.
///
template <bool predicate, typename T, T trueValue, T falseValue>
struct value_if
  : std::integral_constant<T, trueValue>
{ };

//  ***************************************************************************
/// Specialization for the false condition of the value selector.
///
template <typename T, T trueValue, T falseValue>
struct value_if<false, T, trueValue, falseValue>
  : std::integral_constant<T, falseValue>
{ };

Finally, there is a terminating enum_entry specialization, and a template to act as the seed value for the first index.

C++
// Terminating enum_entry. Do not search before line 0.
template<>
struct enum_entry<0, true>
  : std::true_type
{ };

// Initial auto_index specialization to
// start the first index at 0.
template <>
struct auto_index<0>
  : std::integral_constant< int, -1>
{ };

Limitations

Because the INC_COUNTER MACRO depends on the __LINE__ compiler MACRO, each increment definition must be defined on a different line. This also means that INC_COUNTER cannot appear in the same multi-line MACRO more than once. The INC_COUNTER call does not need to appear sequentially. This is because of the search for the last index that was declared, rather than a simple offset from base calculation.

Since the implementation relies on recursive template instantiation to calculate the next index, there is a limitation dependent on the nesting depth of your compiler. The declarations for a set of counting MACROs must be relatively close together as well. Therefore, I recommend that you embed these MACROs inside of other MACROs and create a declaration block with your definitions.

It would probably be possible to modify the counting loop to add conditional statements to take one path for odd numbers, and another for evens. This would effectively double the depth. I did not explore any of these options because what I have presented currently suits my needs. Also, I did not want to over complicate something that I think is already complex.

License

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