When we use a composite matcher Not(m) in a context where a Matcher<T> is expected, what type should we require m to have?
As we have seen, it is too restrictive to demand m being a Matcher<T>, as it prevents you from using Not(an_int_matcher) as a Matcher<short>, even if you can convert the short argument to int before giving it to an_int_matcher.
To design the typing rule here in a non-restrictive yet safe way, we need to distinguish between two kinds of conversions between monomorphic matchers: safe conversions and unsafe conversions:
As an example of a safe conversion, you can use a Matcher<int> (let's call it m) as a Matcher<const int&>: you just pass the argument (typed as const int&) to m, which takes an int. No information will be lost in the process. The converse is not true: if you use a Matcher<const int&> as a Matcher<int>, you may get a wrong result as the Matcher<const int&> may be interested in the address of the argument (as in the example of Ref(x)).
But what dos it exactly mean for a conversion to be safe?
In general, if type A can be implicitly converted to type B, we can safely convert a Matcher<B> to a Matcher<A> (i.e. Matcher is contravariant): just keep a copy of the original Matcher<B>, convert the argument from type A to B, and then pass it to the underlying Matcher<B>. There are two exceptions to this rule:
- When B is a reference and A is not, the conversion is unsafe as the underlying Matcher<B> may be interested in the argument's address, which is not preserved in the conversion from A to B.
- Lossy conversions between built-in numeric types are implicit in C++. That is, as far as the compiler is concerned, you can cast a double to an int implicitly. Therefore, for conversions between built-in numeric types, we impose the additional constraint that they must not be lossy (e.g. converting from int to long is fine, but vice versa is not).
Matcher<int> equals_5 = Eq(5);
Matcher<double> double_equals_5 = SafeMatcherCast<double>(equals_5);
Matcher<double> double_equals_5 = SafeMatcherCast<double>(equals_5);
If you use double_equals_5 to match 5.2, you'll get "yes", even though 5.2 isn't equal to 5.
Before we end this post, let recap the basic typing rules for matchers:
- Matchers are statically typed.
- A monomorphic matcher that matches an int value has type Matcher<int>.
- Matcher<int> and Matcher<const int&> are different types: a matcher of the latter type can see the address of its argument, while a matcher of the former type can't.
- A polymorphic matcher m has a type that can be implicitly converted to Matcher<T> if m can be used to match a T value.
- A Matcher<B> can be safely converted to a Matcher<A> as long as A can be implicitly converted to B, the conversion is not from a non-reference to a reference, and it's not a lossy numeric conversion.