Accessing static members via an instance

We all know about accessing static members using a class name:

class c
{
public:
  static void f ();
  static int i;
};
 
c::f ();
c::i++;

But did you know that we can also access them using a class instance, just like we would ordinary, non-static members?

c x;
 
x.f ();
x.i++;

This always seemed weird to me since a static member doesn’t depend on an instance and, in particular, since a static function does not have the this pointer. I was always wondering what this feature could be useful for. My best guess was some template metaprogramming technique where we don’t know whether a member is static or not. However, I’ve never seen any actual code that relied on this.

Until recently, that is, when I found a perfect use for this feature (this is one of those few benefits of knowing obscure C++ language details; once in a while a problem arises for which you realize there is a quirky but elegant solution).

But first we need a bit of a background on the problem. You may have heard of ODB, which provides object-relational mapping (ORM) for C++. ODB has a C++-integrated query language that allows us to query for persistent objects using a familiar C++ syntax instead of SQL. In other words, ODB query language is a domain-specific language (DSL) embedded into C++. Here is a simple example:

class person
{
  ...
 
  std::string first_;
  std::string last_;
  unsigned short age_;
};

Given this persistent class we can perform queries like this:

typedef odb::query<person> query;
typedef odb::result<person> result;
 
result r (db.query (query::last == "Doe" && query::age < 30));

Here is how this is implemented (in slightly simplified terms): for the person class the ODB compiler will generate the odb::query template specialization that contains static “query columns” corresponding to the data members in the class, for example:

// Generated by the ODB compiler.
//
namespace odb
{
  template <>
  class query<person>
  {
    static query_column<std::string> first;
    static query_column<std::string> last;
    static query_column<unsigned short> age;
  };
}

In turn, the query_column class template overloads various C++ operators (==, !=, <, etc) that translate a C++ expression such as:

query::last == "Doe" && query::age < 30

To an SQL WHERE clause that looks along these lines:

last = $1 AND age < $2

And pass "Doe" for $1 and 30 for $2.

This design worked very well until we needed to add support for composite values and object pointers:

#pragma db object
class employer
{
  ...
 
  std::string name_;
};
 
#pragma db value
struct name
{
  std::string first_;
  std::string last_;
};
 
#pragma db object
class person
{
  ...
 
  name name_;
  unsigned short age_;
  shared_ptr<employer> employer_;
};

The first version of the query language with support for composite values and object pointers used nested scopes to represent both. The generated odb::query specializations in this version would look like this:

namespace odb
{
  template <>
  class query<employer>
  {
    static query_column<std::string> name;
  };
 
  template <>
  class query<person>
  {
    struct name
    {
      static query_column<std::string> first;
      static query_column<std::string> last;
    };
 
    static query_column<unsigned short> age;
 
    typedef query<employer> employer;
  };
}

And an example query would look like this:

query::name::last == "Doe" && query::employer::name == "Example, Inc"

The problem with this query is that it is not very expressive; by looking at it, it is not clear whether the name and employer components correspond to composite values or object pointers. Plus, it doesn’t mimic C++ very well. In C++ we would use the dot operator (.) to access a member in a instance, for example, name.last. Similarly, we would use the arrow operator (->) to access a member via a pointer, for example, employer->name. So what we would want then is to be able to write the above query expression like this:

query::name.last == "Doe" && query::employer->name == "Example, Inc"

Now it is clear that name is a composite value while employer is an object pointer.

The question now is how can we adapt the odb::query specialization to provide this syntax. And that’s where the ability to access a static data member via an instance fits right in. Let’s start with the composite member:

  template <>
  class query<person>
  {
    struct name_type
    {
      static query_column<std::string> first;
      static query_column<std::string> last;
    };
 
    static name_type name;
 
    ...
  };

query::name is now a static data member and we use the dot operator in query::name.last to access its static member.

Things get even more interesting when we consider object pointers. Remember that here we want to use the arrow operator to access nested members. To get this syntax we create this curiously looking, smart pointer-like class template:

template <typename T>
struct query_pointer
{
  T* operator-> () const
  {
    return 0; // All members in T are static.
  }
};

For fun, try showing it to your friends or co-workers and ask them what it could be useful for. Just remember to remove the comment after the return statement ;-). Here is how we use this class template in the odb::query specialization:

  template <>
  class query<person>
  {
    ...
 
    static query_pointer< query<employer> > employer;
  };

When the arrow operator is called in query::employer->name, it returns a NULL pointer. But that doesn’t matter since the member we are accessing is static and the pointer is not used.

If you know of other interesting use cases for the static member access via instance feature, feel free to share them in the comments.

2 Responses to “Accessing static members via an instance”

  1. Simon Dice Says:

    are you sure that this is not an undefined behavior? Look at this discussion for example: http://stackoverflow.com/questions/2474018/when-does-invoking-a-member-function-on-a-null-instance-result-in-undefined-beha

  2. Boris Kolpackov Says:

    Good question. It seems the answer depends on how you interpret the standard. I agree it is probably best to avoid accessing a non-static member via a NULL pointer. In case of a static member, however, I don’t see any logical reason why it cannot be done safely even if one can interpret the standard in such a way that it calls for undefined behavior. From a practical point of view, a compiler implementer will have to intentionally add extra code to cause any harm in this situation, since in the generated code x.foo() and x::foo() will result in equivalent instructions (or, in other words, x.foo() can be reduced to x::foo() at compile time).