I have this minimal expression template library with a multiplication, i.e.
template <typename T, typename U>
struct mul {
const T &v1;
const U &v2;
};
template <typename T, typename U>
mul<T, U> operator*(const T &one, const U &two) {
std::cout << " called: mul<T, U> operator*(const T &one, const T &two)\n";
return mul<T, U>{one, two};
}
and transpose, i.e.
template <typename T>
struct transpose {
const T &t;
};
template <typename T>
transpose<T> tran(const T &one) {
return transpose<T>{one};
}
I will introduce some types A
and B
, where the latter is a subclass of the former:
template <typename T>
struct A {
T elem;
};
template <typename T>
struct B : A<T> {
B(T val) : A<T>{val} {}
};
Then, I can call my expression template library as follows (with an overload for printing to std::cout
):
template <typename T, typename U>
std::ostream &operator<<(std::ostream &os, const mul<T, U> &m) {
os << " unconstrained template \n";
}
int main(int argc, char const *argv[]) {
B<double> a{2};
B<double> b{3};
std::cout << tran(a) * b << "\n";
return 0;
}
This gives me the output :
called: mul<T, U> operator*(const T &one, const T &two)
unconstrained template
So far so good. Suppose now that I want a specialized treatment for 'transpose of a variable of type A<T>
times a variable of type A<T>
for some type T
', as I had in my main
. To this end, I will introduce
template <typename T>
T operator*(const transpose<A<T>> &one, const A<T> &two) {
std::cout << " called: T operator*(const A<T> &one, const A<T> &two)\n";
return one.t.elem * two.elem;
}
I run the same main
function as above, and I still get the same output as above (unconstrained template
). This is to be expected, since transpose<B<double>>
is a completely different type compared to transpose<A<double>>
, so overload resolution picks the unconstrained template version of operator*
.
(Of course, if I change my variable definitions in main
to A
instead of B
, ADL calls the specialized function and output is called: T operator*(const A<T> &one, const A<T> &two)
and 6
).
I recently learned about SFINAE, so I expected the following change to the more specific multiplication operator would cause overload resulution to select the specialized function:
template <typename T, typename V>
std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const transpose<V> &one,
const V &two) {
std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
"transpose<V> &one, const V &two)\n";
return one.t.elem * two.elem;
}
Even using the SFINAE'd operator*
I still get the unconstrained template
version. How come? What changes should I make to call the more specialized template function?
The problem is that in the SFINAE overload, T
is used in a non-deduced context. You're effectively asking the compiler: "Enable this if there exists a T
such that A<T>
is a base class of V
." Existential quantification is a good indicator that what you're asking for cannot be SFINAEd.
You can see this yourself if you disable the unconstrained template, as I did here. This forces the compiler to spell out why the other function is not admissible.
You can solve this by making T
available through your A
(and thus B
) classes, like this:
template <typename T>
struct A {
using Type = T;
T elem;
};
template <typename V>
std::enable_if_t<std::is_base_of<A<typename V::Type>, V>::value, typename V::Type> operator*(const transpose<V> &one,
const V &two) {
std::cout << " called: std::enable_if_t<std::is_base_of<A<T>, V>::value, T> operator*(const "
"transpose<V> &one, const V &two)\n";
return one.t.elem * two.elem;
}
[Live example]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With