|
BDE 4.14.0 Production release
|
Provide a class supporting "do-the-right-thing clause" dispatch.
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.
In this section we show intended use of this component.
Suppose we want to create a container with two constructors:
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.
The problem with the MyProblematicContainer class becomes manifest when we create several objects:
Standard output shows:
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:
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:
Note that, as in the constructors of the MyProblematic class, the privateInit* methods provide display a message so we can trace the call path.
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.
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:
privateInit corresponding to repeated value constructor is invoked from the "strict" overload of privateInitDispatch.privateInit for range construction is invoked from the other privateInitDispatch overload. 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. 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:
Finally, we create three objects of MyContainer, using the same arguments as we used for the three MyProblematicContainer objects.
Standard output shows:
Notice that the repeated value privateInit method is called directly for the second object, but called via privateInitDispatch for the third object.