c++c++14sfinaetype-traits

Type trait to check whether a function can compile with a given type


I have two types, CustomNotCompatibleWithRun, CustomCompatibleWithRun, and a run() function:

#include <iostream>
#include <type_traits>

template <typename T_>
struct CustomNotCompatibleWithRun { using T = T_; };

template <typename T_>
struct CustomCompatibleWithRun {
    using T = T_; 
    
    CustomCompatibleWithRun operator+(const CustomCompatibleWithRun& other)
    {
        CustomCompatibleWithRun res;
        res.a = a + other.a;
        res.b = b + other.b;
        return res;
    }
    
    void print() const
    {
        std::cout << "a, b = " << a << ", " << b << "\n";
    }
    
    T a;
    T b;
};

template <typename T>
T run(T a, T b) { return a+b; }

I want to write a type trait, IsTypeCompatibleWithRun, which checks whether a given Type (whose only contract is to have a type alias Type::T) can be used in the run() function. That is:

int main() {
    using T = float;
    CustomCompatibleWithRun<T> obj1{.a = 1.f, .b = 2.f};
    CustomCompatibleWithRun<T> obj2{.a = 2.f, .b = 3.f};
    obj1.print(); // a, b = 1, 2
    obj2.print(); // a, b = 2, 3
    const auto obj3 = obj1 + obj2;
    obj3.print(); // a, b = 3, 5
    const auto obj4 = run(obj1, obj2);
    obj4.print(); // a, b = 3, 5
    CustomNotCompatibleWithRun<T> obj5{};
    CustomNotCompatibleWithRun<T> obj6{};
    // const auto obj7 = obj5 + obj6; // does not compile
    
    std::cout << "IsTypeCompatibleWithRun<CustomCompatibleWithRun>::value: " << IsTypeCompatibleWithRun<CustomCompatibleWithRun<T>>::value << "\n"; // should print 1
    std::cout << "IsTypeCompatibleWithRun<CustomNotCompatibleWithRun>::value: " << IsTypeCompatibleWithRun<CustomNotCompatibleWithRun<T>>::value << "\n"; // should print 0
}

I have tried the three options below:

template <typename Type, typename = void>
struct IsTypeCompatibleWithRun : std::false_type {};

// Option 1
template <typename Type>
struct IsTypeCompatibleWithRun<Type, decltype(void(run<typename Type::T>(std::declval<typename Type::T>(), std::declval<typename Type::T>())))> : std::true_type {};

// Option 2
template <typename Type>
struct IsTypeCompatibleWithRun<Type, decltype(void(std::declval<Type>() + std::declval<Type>()))> : std::true_type {};

// Option 3
template <typename Type>
struct IsTypeCompatibleWithRun<Type, std::void_t<decltype(run(std::declval<Type>(), std::declval<Type>()))>> : std::true_type {};

And only Option 2 works - from what I understand that is because we force the + operator inside the type trait itself, whereas for Options 1 and 3, only the signature of the run() function is checked? However I'm dissatisfied with Option 2 because it requires repeating the actual function body of run() into the type trait itself. In this simple example it is fine but in my application the body of the run() function is much more complicated and it is impossible to repeat it inside the type trait.

I then tried changing the signature of run() to

template <typename T>
auto run(T a, T b)

but the code would not even compile anymore. I then tried adding a trailing return type

template <typename T>
auto run(T a, T b) -> decltype(a+b)

and now Option 3 would work, but not Option 1. Why is that?

Either way, the above would involve having to repeat the a+b part in the trailing return type so it is not a solution for me.

Is there a way to achieve this type trait without altering run() and solely relying on run() (and not repeating its function body), in C++ 14?


Solution

  • What you're seeing is a limitation of SFINAE (Substitution Failure Is Not An Error) in C++14. This only checks the function signature during template substitution, not the function body if that makes sense.

    Without changing run() and without repeating its body, there's no way to achieve this type trait.

    Hope this helps