How to enable a method only if two types are std::complex or not?

101 Views Asked by At

In a templated class I'd like to enable a function only if a class type and the methods type are std::complex or if both are not complex.

template<typename T>
class M {
public:
   template<typename T2>
   void method(T2 arg) { }
};

I'd like to the enable method only if T and T2 are std::complex or if both are not std::complex.

I found How to identifying whether a template argument is std::complex? to get a template telling me how to check whether a type is std::complex-based or not. But I'm struggling with the formulation of the template-usage to use std::enable_if. I tried something with std::is_same, I haven't found how to do it. Then there is a way where you get the return-type from within std::enable_if, but again it didn't work as expected.

2

There are 2 best solutions below

6
sehe On

You can deduce template arguments:

 template<typename T2>
 void method(std::complex<T2> arg) { }

A more complete example:

Live On Coliru

#include <complex>
#include <iostream>

template <typename T> class M {
    public:
      template <typename T2> void method(T2 const&) const {
          std::cout << "Primary template " << __PRETTY_FUNCTION__ << "\n";
      }

      template <typename U> void method(std::complex<U> const&) const = delete;
};

template <typename V> class M<std::complex<V> > {
    public:
      void method(std::complex<V> const&) const {
          std::cout << "Specialization " << __PRETTY_FUNCTION__ << "\n";
      }
};

int main() {
    {
        M<std::pair<int, double> > m;
        m.method(std::string("hello"));
        m.method(3.1415926);
    }
    {
        M<std::complex<double> > m;
        // m.method(std::string("hello")); // doesn't compile, not std::complex
        m.method(3.1415926);
    }
}

Printing

Primary template void M<T>::method(const T2&) const [with T2 = std::__cxx11::basic_string<char>; T = std::pair<int, double>]
Primary template void M<T>::method(const T2&) const [with T2 = double; T = std::pair<int, double>]
Specialization void M<std::complex<_Tp> >::method(const std::complex<_Tp>&) const [with V = double]
8
463035818_is_not_an_ai On

Disclaimer: I did not consider implicit conversions, because you asked for T2 to be std::complex. See at the end for what I mean.

You can write a trait to detect if some type is an instantiation of std::complex. Then you can use std::enable_if for return type SFINAE:

#include <type_traits>
#include <complex>

template <typename T>
struct is_complex : std::false_type {};

template <typename U>
struct is_complex< std::complex<U> > : std::true_type {};



template<typename T>
class M {
public:
   template<typename T2>
   std::enable_if_t<is_complex<T>::value == is_complex<T2>::value> method(T2 arg) { }
};

std::enable_if_t< condition > is void when the condition is true and a substitution failure otherwise.


Implicit converions:

The above only works when T2 is deduced to be some instantiation of std::complex. So far so good. But it does not work when you call the method with some argument that can implicitly be converted to a std::complex. For example:

 struct foo {
     operator std::complex<double>() { return {}; }
 };

 int main() {
     M<std::complex<double>> m;
     foo f;
     std::complex<double> c = f;   // OK !
     m.method(f);                  // fails :/
 }

This is somewhat unexpected unless you delibaretly want to disable implicit conversion. For a solution that does allow for implicit conversions see this answer.


PS: There is a proposal to add a std::is_complex: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2078r0.pdf. I just found it and don't know what happened to it. In my opinion, its not something that we need in the standard library, because next we will have is_vector, is_map and is_unordered_map and is_foo and is_bar.