Quick Links:

bal | bbl | bdl | bsl

Namespaces

Component bslmf_matcharithmetictype
[Package bslmf]

Provide a class supporting "do-the-right-thing clause" dispatch. More...

Namespaces

namespace  bslmf

Detailed Description

Outline
Purpose:
Provide a class supporting "do-the-right-thing clause" dispatch.
Classes:
bslmf::MatchArithmeticType implicit conversion of arithmetic types
See also:
Component bslmf_matchanytype, Component bslstl_deque, Component bslstl_string, Component bslstl_vector
Description:
This component defines a class, bslmf::MatchArithmeticType, to which any arithmetic type can be implicitly converted. A class with that conversion property is useful for meeting the certain requirements of the standard sequential containers (e.g., bsl::vector, bsl::deque, bsl::string).
Sequential containers have several overloaded method templates that accept a pair of input iterators (e.g., constructors, insert and append methods), but which must not accept arithmetic types (e.g., bool, char, short, int, double). See "ISO/IEC 14882:2011 Programming Language C++" (see http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf), "Section 23.2.3 [sequence.reqmts]", paragraphs 14-15. This requirement is informally known as the "do-the-right-thing clause". See http://gcc.gnu.org/onlinedocs/libstdc++/ext/lwg-defects.html#438.
The convertibility of arguments to bslmf::MatchArithmeticType is used to dispatch calls with arithmetic arguments to the appropriate methods. Note that this technique (a variation of "tag dispatch") can be compromised if one uses a class that defines a conversion operator to bslmf::MatchArithmeticType (or a conversion operator to bslmf::MatchAnyType) but otherwise do not behave as arithmetic types.
Enumerations (not arithmetic types themselves) are implicitly convertible to blsmf::MatchArithmeticType, and will dispatch as some integer (a sub-set of arithmetic) type.
Usage:
In this section we show intended use of this component.
Example 1: "Do-the-Right-Thing" Clause Dispatch:
Suppose we want to create a container with two constructors:
  • one constructor providing initialization with multiple copies of a single value (a "repeated value constructor"), and
  • the other providing initialization from a sequence of values delimited by a pair of iterators (a "range constructor").
A naive implementation can result in common usage situations in which arguments meaningful to the former constructor are provided but where the compiler resolves the overload to the latter constructor.
For example, the MyProblematicContainer class outlined below provides two such constructors. Note that each is atypically embellished with a message parameter, allowing us to trace the call flow.
                      // ============================
                      // class MyProblematicContainer
                      // ============================

  template <class VALUE_TYPE>
  class MyProblematicContainer {

      // ...

    public:
      // CREATORS
      MyProblematicContainer(std::size_t        numElements,
                             const VALUE_TYPE&  value,
                             const char        *message);
          // Create a 'MyProblematicContainer' object containing the
          // specified 'numElements' of the specified 'value', and write to
          // standard output the specified 'message'.

      template <class INPUT_ITER>
      MyProblematicContainer(INPUT_ITER  first,
                             INPUT_ITER  last,
                             const char *message);
          // Create a 'MyProblematicContainer' object containing the values
          // in the range starting at the specified 'first' iterator and
          // ending immediately before the specified 'last' iterator of the
          // 'INPUT_ITER' type, and write to standard output the specified
          // 'message'.

      // ...

  };

  // ========================================================================
  //                      INLINE FUNCTION DEFINITIONS
  // ========================================================================

                      // ============================
                      // class MyProblematicContainer
                      // ============================

  // CREATORS
  template <class VALUE_TYPE>
  MyProblematicContainer<VALUE_TYPE>::MyProblematicContainer(
                                              std::size_t        numElements,
                                              const VALUE_TYPE&  value,
                                              const char        *message)
  {
      assert(message);
      printf("CTOR: repeated value: %s\n", message);
      // ...
  }

  template <class VALUE_TYPE>
  template <class INPUT_ITER>
  MyProblematicContainer<VALUE_TYPE>::MyProblematicContainer(
                                                         INPUT_ITER  first,
                                                         INPUT_ITER  last,
                                                         const char *message)
  {
      assert(message);
      printf("CTOR: range         : %s\n", message);
      // ...
  }
The problem with the MyProblematicContainer class becomes manifest when we create several objects:
      const char                   input[] = "How now brown cow?";
      MyProblematicContainer<char> initFromPtrPair(
                                                input,
                                                input + sizeof(input),
                                                "Called with pointer pair.");

      MyProblematicContainer<char> initFromIntAndChar(
                                            5,
                                            'A',
                                            "Called with 'int' and 'char'.");

      MyProblematicContainer<char> initFromIntAndInt(
                                            5,
                                            65, // 'A'
                                            "Called with 'int' and 'int'.");
Standard output shows:
  CTOR: range         : Called with pointer pair.
  CTOR: repeated value: Called with 'int' and 'char'.
  CTOR: range         : Called with 'int' and 'int'.
Notice that the range constructor, not the repeated value constructor, is invoked for the creation of initFromIntAndInt, the third object.
The range constructor is chosen to resolve that overload because its match of two arguments of the same type (int in this usage) without conversion is better than that provided by the repeated value constructor, which requires conversions of two different arguments.
Note that, in practice, range constructors (expecting iterators) dereference their arguments, and so fail to compile when instantiated with arithmetic types.
If we are fortunate, range constructor code will fail to compile; otherwise, dereferencing integer values (i.e., using them as pointers) leads to undefined behavior.
Note that, in many other situations, overloading resolution issues can be avoided by function renaming; however, as these are constructors, we do not have that option.
Instead, we redesign our class (MyContainer is the redesigned class) so that the calls to the range constructor with two int arguments (or pairs of the same integer types) are routed to the repeated value constructor. The bslmf::MatchArithmeticType class is used to distinguish between integer types and other types.
First, we define the MyContainer class to have constructors taking the same arguments as the constructors of MyProblematicContainer:
                      // =================
                      // class MyContainer
                      // =================

  template <class VALUE_TYPE>
  class MyContainer {

      // ...

    public:
      // CREATORS
      MyContainer(std::size_t        numElements,
                  const VALUE_TYPE&  value,
                  const char        *message);
          // Create a 'MyProblematicContainer' object containing the
          // specified 'numElements' of the specified 'value', and write to
          // standard output the specified 'message'.

      template <class INPUT_ITER>
      MyContainer(INPUT_ITER  first, INPUT_ITER  last, const char *message);
          // Create a 'MyProblematicContainer' object containing the values
          // in the range starting at the specified 'first' and ending
          // immediately before the specified 'last' iterators of the type
          // 'INPUT_ITER', and write to standard output the specified
          // 'message'.

      // ...

 };
Then, we isolate the essential actions of our two constructors into two private, non-creator methods. This allows us to achieve the results of either constructor, as appropriate, from the context of the range constructor. The two privateInit* methods are:
    private:
      // PRIVATE MANIPULATORS
      void privateInit(std::size_t        numElements,
                       const VALUE_TYPE&  value,
                       const char        *message);
          // Initialize a 'MyContainer' object containing the specified
          // 'numElements' of the specified 'value', and write to standard
          // output the specified 'message'.

      template <class INPUT_ITER>
      void privateInit(INPUT_ITER  first,
                       INPUT_ITER  last,
                       const char *message);
          // Initialize a 'MyContainer' object containing the values in the
          // range starting at the specified 'first' and ending immediately
          // before the specified 'last' iterators of the type 'INPUT_ITER',
          // and write to standard output the specified 'message'.
Note that, as in the constructors of the MyProblematic class, the privateInit* methods provide display a message so we can trace the call path.
  // PRIVATE MANIPULATORS
  template <class VALUE_TYPE>
  void MyContainer<VALUE_TYPE>::privateInit(std::size_t        numElements,
                                            const VALUE_TYPE&  value,
                                            const char        *message)
  {
      assert(message);
      printf("INIT: repeated value: %s\n", message);
      // ...
  }

  template <class VALUE_TYPE>
  template <class INPUT_ITER>
  void MyContainer<VALUE_TYPE>::privateInit(INPUT_ITER  first,
                                            INPUT_ITER  last,
                                            const char *message)
  {
      assert(message);
      printf("INIT: range         : %s\n", message);
      // ...
  }
Now, we define two overloaded privateInitDispatch methods, each taking two parameters (the last two) which serve no run-time purpose. As we shall see, they exist only to guide overload resolution at compile-time.
      template <class INTEGER_TYPE>
      void privateInitDispatch(INTEGER_TYPE                numElements,
                               INTEGER_TYPE                value,
                               const char                 *message,
                               bslmf::MatchArithmeticType  ,
                               bslmf::Nil                  );
          // Initialize a 'MyContainer' object containing the specified
          // 'numElements' of the specified 'value', and write to standard
          // output the specified 'message'.  The last two arguments are used
          // only for overload resolution.

      template <class INPUT_ITER>
      void privateInitDispatch(INPUT_ITER           first,
                               INPUT_ITER           last,
                               const char          *message,
                               bslmf::MatchAnyType  ,
                               bslmf::MatchAnyType  );
          // Initialize a 'MyContainer' object containing the values in the
          // range starting at the specified 'first' and ending immediately
          // before the specified 'last' iterators of the type 'INPUT_ITER',
          // and write to standard output the specified 'message'.  The last
          // two arguments are used only for overload resolution.
Notice that the first overload has strict requirements on the last two arguments, but the second overload (accepting bslmf::MatchAnyType in those positions) will match all contexts in which the first fails to match.
Then, we implement the two privateInitDispatch overloads so that each invokes a different overload of the privateInit methods:
  • The privateInit corresponding to repeated value constructor is invoked from the "strict" overload of privateInitDispatch.
  • The privateInit for range construction is invoked from the other privateInitDispatch overload.
  template <class VALUE_TYPE>
  template <class INTEGER_TYPE>
  void MyContainer<VALUE_TYPE>::privateInitDispatch(
                                     INTEGER_TYPE                numElements,
                                     INTEGER_TYPE                value,
                                     const char                 *message,
                                     bslmf::MatchArithmeticType  ,
                                     bslmf::Nil                  )
  {
      (void) message;

      privateInit(static_cast<std::size_t>(numElements),
                  static_cast<VALUE_TYPE>(value),
                  "Called via 'privateInitDispatch'.");
  }

  template <class VALUE_TYPE>
  template <class INPUT_ITER>
  void MyContainer<VALUE_TYPE>::privateInitDispatch(
                                                INPUT_ITER           first,
                                                INPUT_ITER           last,
                                                const char          *message,
                                                bslmf::MatchAnyType  ,
                                                bslmf::MatchAnyType  )
  {
      privateInit(first, last, message);
  }
Next, we use overloaded privateInitDispatch method in the range constructor of MyContainer. Note that we always supply a bslmf::Nil object (an exact type match) as the final argument, the choice of overload will be governed according to the type of first. Consequently, if first is implicitly convertible to bslmf::MatchArithmeticType, then the overload leading to repeated value construction is used; otherwise, the overload leading to range construction is used.
  template <class VALUE_TYPE>
  template <class INPUT_ITER>
  MyContainer<VALUE_TYPE>::MyContainer(INPUT_ITER  first,
                                       INPUT_ITER  last,
                                       const char *message)
  {
      privateInitDispatch(first, last, message, first, bslmf::Nil());
  }
Notice that this design is safe for iterators that themselves happen to have a conversion to int. Such types would require two user-defined conversions, which are disallowed by the C++ compiler, to match the bslmf::MatchArithmeticType parameter of the "strict" privateInitDispatch overload.
Then, we implement the repeated value constructor using a direct call to the repeated value private initializer:
  // CREATORS
  template <class VALUE_TYPE>
  MyContainer<VALUE_TYPE>::MyContainer(std::size_t        numElements,
                                       const VALUE_TYPE&  value,
                                       const char        *message)
  {
      privateInit(numElements, value, message);
  }
Finally, we create three objects of MyContainer, using the same arguments as we used for the three MyProblematicContainer objects.
      const char        input[] = "How now brown cow?";
      MyContainer<char> initFromPtrPair(input,
                                        input + sizeof(input),
                                        "Called with pointer pair.");

      MyContainer<char> initFromIntAndChar(5,
                                           'A',
                                           "Called with 'int' and 'char'.");

      MyContainer<char> initFromIntAndInt(5,
                                          65, // 'A'
                                          "Called with 'int' and 'int'.");
Standard output shows:
  INIT: range         : Called with pointer pair.
  INIT: repeated value: Called with 'int' and 'char'.
  INIT: repeated value: Called via 'privateInitDispatch'.
Notice that the repeated value privateInit method is called directly for the second object, but called via privateInitDispatch for the third object.