Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 20.  Shims


20.7. Namespaces and Koenig Lookup

The picture I've painted of shims seems almost perfect:[14] we've enhanced maintainability (comprehensibility/readability and modifiability), generality (reuse), all without sacrificing performance (when used correctly, anyway). But this is the real world, and nothing's ever perfect. As well as the restriction in the use of access shims to immediate expressions, there is another small problem.

[14] At least I hope you think so!

So far I've not made any mention of namespaces, and all the examples appear to be in the global namespace. If all the types for which the shim you're using is defined are user-defined types, and are defined within the namespaces of their "shimmed" type, and the compiler(s) you are using support(s) Koenig lookup,[15] then you do not need to specify any using declarations, and the whole thing works like a treat. Got all that?

[15] Digital Mars prior to v8.34, Visual C++ prior to v7.1, and Watcom do not support Koenig lookup

Koenig lookup (C++-98: 3.4.2)—also known as Argument-dependent Lookup [Vand2003]) —is the mechanism whereby symbols from other namespaces may be accessed in another namespace, without being introduced via using declarations or directives (or typedef declarations), by virtue of being associated with a symbol that has been introduced. For example, in the following code the function f is defined with namespace ns and is not introduced into the (global) namespace of the g() function. However, because the variable s is of type S, which is defined in the namespace ns, f can be looked up in the namespace of S.

Listing 20.11.


namespace ns


{


  struct S


  {};


  void f(S &)


  {}


}


void g()


{


  ns::S s;


  f(s); // f looked up in same namespace as S


}



Unfortunately, this being the real world, several compilers do not fully implement Koenig lookup. Furthermore, we often want to define shims to include basic types and those defined within the global namespace. In such cases, we need to use using declarations between the declaration of the types and the algorithms, classes, or client code that is implemented in terms of the shims. With the majority of the time this is simple both to do and to understand, but there are occasional confusions when dealing with heavily derivative code.

In the long run, it'd be nice for popular shims (such as get_ptr, c_str_ptr/c_str_len) to be declared and defined in the namespace along with their requisite types, including those in the std namespace, for example, c_str_ptr(basic_string<T> const &). That is unlikely to happen soon, if at all, so you'll need to bear in mind that you may have to "use" them to use them.

Furthermore, several types for which we'd want to define shims exist in the global namespace (e.g., char const*, struct dirent, LSA_UNICODE_STRING). Because it is more than a little presumptuous to define shims in the global namespace or even in the std namespace,[16] I define all my shims within the stlsoft namespace. This serves the dual purposes of not polluting the global namespace and also of having a single namespace within which all the shims that I use are defined. For any components using the shims that are defined within the stlsoft namespace, or within any of its sub-namespaces [Wils2003b], they pick up the necessary shim definitions even when Koenig lookup does not apply (whether that is because the type is a fundamental type or whether the compiler does not support it).

[16] In fact, the standard prescribes that the only things you may add to the std namespace are specializations of templates that exist in std.

For classes that I write that are not part of STLSoft, for example, application code, I simply define their shims along with the components—either in an application-specific namespace or within the global namespace—and then "use" them within the stlsoft namespace. Let's have a look how this works in practice.

Consider some client code, in the namespace client, which uses the third-party component tp_string that resides in the third_party namespace and the Win32 LSA_UNICODE_STRING type. In order to write our client code to select the c_str_ptr shim for either type, we need to ensure that the requisite shim functions are visible in the client namespace. Listing 20.12 shows one way that this is achieved.

Listing 20.12.


// tp_string.h


namespace third_party


{


  class tp_string


  {


    . . .


    wchar_t const *c_str() const;





  };


  inline wchar_t const *c_str_ptr(tp_string const &s)


  {


    return s.c_str();


  }


}


// Client.cpp


namespace Client


{


  using stlsoft::c_str_ptr;     // for LSA_UNICODE_STRING


#if !defined(ACMELIB_COMPILER_SUPPORTS_KOENIG_LOOKUP)


  using third_party::c_str_ptr; // for tp_string


#endif /* ! ACMELIB_COMPILER_SUPPORTS_KOENIG_LOOKUP */





  template <typename S>


  void puts(S const &s)


  {


    ::putws(c_str_ptr(s));


  }


}



The first using declaration is necessary because LSA_UNICODE_STRING type is defined in the global namespace, so Koenig lookup does not apply. The second using declaration is only needed when the compiler does not support Koenig lookup. But really we've got four lines of gunk when we only need one. The better way to do this is to add the following to the end of tp_string.h:



namespace stlsoft


{


  using third_party::c_str_ptr;


}



Now the preprocessor compiler discrimination and the third_party using declaration can be omitted, leaving us with a simple clean using stlsoft::c_str_ptr. In your own work, you should follow this convention: define the namespace within which your shims will reside, and then either define them in that namespace, or introduce them into that namespace via a using declaration. In any code, whether library or application, that uses the shims, you have one using declaration to introduce all potentially applicable shim functions.

Naturally, in the long run, I'd like to see shims incorporated into the standard library. What I'd suggest in that case, however, would be that shims are given their own namespace—std::shims—within which users would be free to define their own overloads of extant shims or to introduce their own shims.


      Previous section   Next section