RFS
RFS

Reputation: 351

C++ template selection - unusual case

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

Answers (2)

Daniel Langr
Daniel Langr

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

divinas
divinas

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

Related Questions