Reputation: 351
I have the following defintions
template <typename T1, typename T2> class ArithmeticType {
public:
T1 x1;
T2 x2;
typedef typeof(x1+x2) Type;
};
template <typename D1, typename D2> inline
std::complex<typename ArithmeticType<D1,D2>::Type>
operator+(const std::complex<D1>& x1, const std::complex<D2>& x2) {
D1 x1r = real(x1);
D1 x1i = imag(x1);
D2 x2r = real(x2);
D2 x2i = imag(x2);
return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2r, x1i+x2i);
}
I then use this class as follows
std::complex<double> x;
std::cout << typeid(x).name() << std::endl;
ArithmeticType<std::complex<double>,std::complex<float>>::Type y;
std::cout << typeid(y).name() << std::endl;
which the produces output
St7complexIdE
St7complexIdE
which makes sense to me: y
is of type std::complex<double>
Now, with the indent of adding complex and floating types, I add a specialization of the operator+
template so the code becomes
template <typename T1, typename T2> class ArithmeticType {
public:
T1 x1;
T2 x2;
typedef typeof(x1+x2) Type;
};
template <typename D1, typename D2> inline
std::complex<typename ArithmeticType<D1,D2>::Type>
operator+(const std::complex<D1>& x1, const std::complex<D2>& x2) {
D1 x1r = real(x1);
D1 x1i = imag(x1);
D2 x2r = real(x2);
D2 x2i = imag(x2);
return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2r, x1i+x2i);
}
template <typename D1, typename D2> inline
std::complex<typename ArithmeticType<D1,D2>::Type>
operator+(const std::complex<D1>& x1, const D2 x2) {
D1 x1r = real(x1);
D1 x1i = imag(x1);
return std::complex<typename ArithmeticType<D1,D2>::Type>(x1r+x2, x1i+x2);
}
I run the same code as before but now I get a compile error because the compiler tries to use the second specialization instead of the first. This doesn't make sense to me. I would think that the (g++) compiler would still choose the first specialization.
Can some explain the rule why this occurs?
Upvotes: 5
Views: 113
Reputation: 23517
I believe you are observing behavior caused by this quote from cppreference/SFINAE:
Only the failures in the types and expressions in the immediate context of the function type or its template parameter types are SFINAE errors. If the evaluation of a substituted type/expression causes a side-effect such as instantiation of some template specialization, generation of an implicitly-defined member function, etc, errors in those side-effects are treated as hard errors.
A minimal example:
template <typename T>
struct has_foo
{
using foo_t = decltype(T{}.foo());
};
template <typename T> void f(T&&) { }
// SFINAE error => no compilation error:
template <typename T> decltype(T{}.foo()) f(T&&) { }
// non-SFINAE error => hard error:
template <typename T> typename has_foo<T>::foo_t f(T&&) { }
int main()
{
f(1);
}
Your code basically behaves the same, just instead of has_foo
you have ArithmeticType
.
As for the return type deduction with auto
, see, for example: SFINAE with C++14 return type deduction.
A working example with the same has_foo
as above:
#ifndef DEFINE_THIS_SYMBOL_TO_CAUSE_COMPILATION_ERROR
void f(int) { }
#endif
// no SFINAE => viable candidate
template <typename T> auto f(T&&)
{
return typename has_foo<T>::foo_t{};
}
int main()
{
f(1);
}
If void f(int) { }
is defined, its a better match and the templated f
is not instantiated.
Upvotes: 2
Reputation: 1907
The problem with the second template is the deduction of the return value:
template <typename D1, typename D2> inline
std::complex<typename ArithmeticType<D1,D2>::Type>
operator+(const std::complex<D1>& x1, const D2 x2)
When you try to instantiate ArithmeticType<std::complex<double>, std::complex<float> >
The second operator+
types are deduced to:
D1: double
, D2: std::complex<float>
. You then try to instantiate ArithmeticType
with D1=double
, D2=std::complex<float>
. This in turn, tries to deduce the type of operator+
for a double
and std::complex<float>
. There is no such function, and you fail to compile.
Upvotes: 1