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.