c++templatessubstitutionoverload-resolutiontmp

Avoiding template parameter substitution completely


I have a class that can accept arithmetic types and std::complex. A simplified code of the class is

#include <complex>

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

template<class T>
struct Foo {
    void foo(typename T::value_type t)
    requires (is_complex<T>::value) {
    }
};

Now, I would like to take the internal type of std::complex and use it as the type of the parameters in the foo function.For example, if T is std::complex<double>, then I want the parameter types to be double.

This function should only be available when T is indeed std::complex.

I thought I could use typename T::value_type as the parameter type, since std::complex has a typedef value_type. Plus, I thought using requires here would avoid T to be substitued in this function in case T wasn't std::complex. Silly me. The issue is that whenever I create a Foo<FundamentalType> the code breaks, since fundamentals don't have ::value_type.

int main() {
    Foo<int> obj; // Breaks the code.
    //obj.foo(4); // Function shouldn't be considered in overload resolution ideally...

    Foo<std::complex<int>> obj2; // Works
    obj2.foo(4); // Works as expected
}

Ideally, I would like the substitution of T to be ignored for this function in case T is not std::complex. Is that possible? If not, how can I circumvent this?


Solution

  • You're on the right track with is_complex: you'd like the same here, but with a different body of the type. For example,

    template<typename T> struct complex_value_type {};
    template<typename T> struct complex_value_type<std::complex<T>> { using type = T; };
    
    template<typename T>
    using complex_value_type_t = typename complex_value_type<T>::type;
    

    Then, at any point, you can call it as complex_value_type_t<T>:

    template<class T>
    struct Foo {
        template<typename T_ = T>
        void foo(complex_value_type_t<T_> t)
        requires (is_complex<T_>::value) {
        }
    };
    

    The requires is not absolutely necessary then; it's already covered by complex_value_type_t<T> being defined only for complex<T>.