Search code examples
c++template-meta-programmingsfinaec++20function-call-operator

Check if a class has a possibly-overloaded function call operator


I am wondering whether it would be possible to implement a trait in C++20 to check if a type T is such that it has a possibly overloaded/possibly templated function call operator: operator().

// Declaration
template <class T>
struct has_function_call_operator;

// Definition
???  

// Variable template
template <class T>
inline constexpr bool has_function_call_operator_v 
= has_function_call_operator<T>::value;

so that a code such as the following would lead to the correct result:

#include <iostream>
#include <type_traits>

struct no_function_call_operator {
};

struct one_function_call_operator {
    constexpr void operator()(int) noexcept;
};

struct overloaded_function_call_operator {
    constexpr void operator()(int) noexcept;
    constexpr void operator()(double) noexcept;
    constexpr void operator()(int, double) noexcept;
};

struct templated_function_call_operator {
    template <class... Args>
    constexpr void operator()(Args&&...) noexcept;
};

struct mixed_function_call_operator
: overloaded_function_call_operator
, templated_function_call_operator {
};

template <class T>
struct has_function_call_operator: std::false_type {};

template <class T>
requires std::is_member_function_pointer_v<decltype(&T::operator())>
struct has_function_call_operator<T>: std::true_type {};

template <class T>
inline constexpr bool has_function_call_operator_v 
= has_function_call_operator<T>::value;

int main(int argc, char* argv[]) {
    std::cout << has_function_call_operator_v<no_function_call_operator>;
    std::cout << has_function_call_operator_v<one_function_call_operator>;
    std::cout << has_function_call_operator_v<overloaded_function_call_operator>;
    std::cout << has_function_call_operator_v<templated_function_call_operator>;
    std::cout << has_function_call_operator_v<mixed_function_call_operator>;
    std::cout << std::endl;
}

Currently it prints 01000 instead of 01111. If it is not doable in the broadest possible sense, it can be assumed that T is inheritable if that helps. The weirdest possible template metaprogramming tricks are welcome as long as they are fully compliant with the C++20 standard.


Solution

  • &T::operator() is ambiguous for the 3 failing cases.

    So your traits found is there is an unambiguous operator()

    As you allow T to be not final, we might apply your traits to (fake) class with existing inherited operator() and class to test:

    template <class T>
    struct has_one_function_call_operator: std::false_type {};
    
    template <class T>
    requires std::is_member_function_pointer_v<decltype(&T::operator())>
    struct has_one_function_call_operator<T>: std::true_type {};
    
    struct WithOp
    {
        void operator()() const;  
    };
    
    template <typename T>
    struct Mixin : T, WithOp {};
    
    // if T has no `operator()`, Mixin<T> has unambiguous `operator()` coming from `WithOp`
    // else Mixin<T> has ambiguous `operator()`
    template <class T>
    using has_function_call_operator =
        std::bool_constant<!has_one_function_call_operator<Mixin<T>>::value>;
    
    template <class T>
    inline constexpr bool has_function_call_operator_v 
    = has_function_call_operator<T>::value;
    

    Demo