Previous section   Next section

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


20.5. Conversion Shims

Definition: Conversion Shims

Conversion shims operate by converting instances of the range of compatible types to a single target type.

The names of attribute shims take the form to_xxx, where xxx is the name of, or represents, the target type, for example, to_int.

The values returned from conversion shims may be provided by intermediate temporary objects, so must only ever be used from within the expression containing the shim.


We're going to see a lot more about conversion in the next subsection, so we'll leave most of this for then. However, an interesting example—since it demonstrates an alternate shim implementation strategy—is to be found in the implementation of STLSoft's string_tokeniser template,[8] which we had a little peek at in section 18.5.3. This behemoth of a template takes a whopping six template parameters—the excuse for this is that it's very fast [Wils2004a, Wils2003e]—for stipulating various permutations of string tokenization, such as delimiter type, what to do with blanks, and so on. Thankfully, all but two of these parameters are defaulted; these two are the type used to store the copy of the string to be tokenized (S) and the type of the iterator's value_type (V).

[8] This is available on the CD, along with sufficiently helpful and exemplifying test programs.



template< typename S /* string type, e.g string */


        , typename D /* delimiter type, e.g. char or wstring */


        , typename B = string_tokeniser_ignore_blanks<true>


        , typename V = S /* value type */


        , typename T = string_tokeniser_type_traits<S, V>


        , typename P = string_tokeniser_comparator<D, S, T>


        >


class string_tokeniser;



Dereferencing the iterator via operator *() to obtain its current value entails the creation of an instance of V. Since it is a template parameter, the string_tokeniser needs a mechanism to create the strings, and uses a control shim to do it.

The traits parameter, T, is used to abstract the manipulation of the types S and V. One of its main responsibilities is to define a static method called create(), which takes two parameters (of type S::const_iterator) that must return an instance of V. Hence T::create() is a control shim, and is an example of a shim implemented via the traits [Lipp1998] mechanism.

It defaults to string_tokeniser_type_traits<S, V>, which assumes that S and V both conform to the standard library's String model (C++-98: 21.3). The implementation of its create() is as follows:



static V create(S::const_iterator f, S::const_iterator t)


{


  return V(f, t);


}



This default implementation works for any string type that can be constructed from a bounding iterator range. To implement the shim for another string type can be done either by specializing the string_tokeniser_type_traits template, or by providing a custom traits type. In either case, you have effectively expanded the shim definition to incorporate the new type.

Imagine for a moment that we wanted to use the string_tokeniser with MFC's CString.[9] This class does not define any member types, so at first glance it would seem like a bit of a job to make it work. We have two choices. First, we could specialize stlsoft:: string_tokeniser_type_traits for MFC's CString:

[9] You only have to imagine it. I'm not going to actually make you do it.

Listing 20.4.


namespace stlsoft


{


  template <>


  struct string_tokeniser_type_traits<CString>


  {


    . . .


    static CString create(TCHAR const *f, TCHAR const *t)


    {


      return CString(f, t – f);


    }


  };


};



We could then use it to populate a Win32 list-box control with the contents of the INCLUDE environment variable, using a windows control inserter functor [Wils2003g, Muss2001], as in:



CString                                    include = . . .


stlsoft::string_tokeniser<CString, TCHAR>  tokens(include, ';');





for_each(tokens.begin(), tokens.end(),


              listbox_back_inserter(hwndListBox));



The alternative would be to define our own custom traits type:



stlsoft::string_tokeniser< CString


                         , TCHAR


                         , stlsoft::string_tokeniser_ignore_blanks<true>


                         , CString_tokeniser_type_traits


                         >  tokens(include, ';');



The latter is more verbose as shown, but you could wrap the tokenizer parameterization in a typedef, so there's no great hardship. The benefit is that you're not poking something into another namespace, which has practical and theoretical difficulties, as we see in section 20.8.

(There's actually a third alternative in this specific case, but this uses a technique we look at in the next chapter; see section 21.3.)


      Previous section   Next section