简体   繁体   中英

Unified way for checking the existence of member functions, free functions and operators

I have found several several questions on here that dealt with checking if either a member function, a free function or an operator exists for a given type. The proposed solutions solve the problem at hand but each uses a different approach. I am trying to find a way to deal with each of those problems in an identical or at least similar fashion.

Checking if a Type C has a member func works:

template<typename, typename T>
    struct has_member {
        static_assert(
            std::integral_constant<T, false>::value,
            "Second template parameter needs to be of function type.");
};

template<typename C, typename Ret, typename... Args>
struct has_member<C, Ret(Args...)> {
private:
    template<typename T, std::enable_if_t<
        std::is_same
        <
            decltype(std::declval<T>().func(std::declval<Args>()...)),
            Ret   
        >::value 
    > * = nullptr >
    static constexpr std::true_type check(T*);

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(nullptr)) type;

public:
    static constexpr bool value = type::value;
};

This is taken from the most upvoted answer of this question: Check if a class has a member function of a given signature The condition for compilation has just been moved from the return type to a template parameter.

Checking if operator+ exists is also okay:

template<typename C, typename Ret, typename Arg>
struct has_operator {
private:
    template<typename T, std::enable_if_t<
        std::is_same
        <
            decltype(std::declval<T>() + std::declval<Arg>()),
            Ret
        >::value
    > * = nullptr >
    static constexpr std::true_type check(T*);

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(nullptr)) type;

public:
    static constexpr bool value = type::value;
};

Checking, if a free function free_func exists however does not work:

template<typename T>
    struct has_function {
        static_assert(
            std::integral_constant<T, false>::value,
            "Second template parameter needs to be of function type.");
};

template<typename Ret, typename... Args>
struct has_function<Ret(Args...)> {
private:
    template<std::enable_if_t
    <
        std::is_same
        <
            decltype(free_func(std::declval<Args>()...)),
            Ret    
        >::value
    > * = nullptr >
    static constexpr std::true_type check(nullptr_t);

    template<typename = void>
    static constexpr std::false_type check(...);

    typedef decltype(check<>(nullptr)) type;

public:
    static constexpr bool value = type::value;
};

With the following declarations:

struct MyStruct{
    int func(double);
    MyStruct operator+(const MyStruct &);
};

int free_func(double);

I get these results:

std::cout << has_member<MyStruct, int(double)>::value << std::endl; // true
std::cout << has_member<MyStruct, int(double, double)>::value << std::endl; // false

std::cout << has_function<int(double)>::value << std::endl; // true
//std::cout << has_function<int(double, double)>::value << std::endl; // compile error: free_func does not take 2 arguments

std::cout << has_operator<MyStruct, MyStruct, MyStruct>::value << std::endl; // true
std::cout << has_operator<int, int, int>::value << std::endl; // true
std::cout << has_operator<std::vector<int>, std::vector<int>, std::vector<int>>::value << std::endl; // false

My question now is: What am I doing wrong while trying to check if a free function with a given name and signature exists? If I remove the first declaration of check(Ret*) the other template is instanciated and correctly evaluated to false . I am assuming I make some mistake so that SFINAE is not applicable here.

I also tried adding another template parameter to check , however without changing the outcome.

template<typename T, std::enable_if_t
<
    std::is_same
    <
        decltype(free_func(std::declval<Args>()...)),
        Ret
    >::value
> * = nullptr >
static constexpr std::true_type check(T *);

template<typename>
static constexpr std::false_type check(...);

typedef decltype(check<Ret>(nullptr)) type;

I would like to keep using the decltype(declval(...)) style since it allows the compiler to figure out if anything callable exists and I do not have to care about whether the function takes its arguments by value, by reference or by const reference.

Any help is appreciated. Thank you very much in advance.

An additional thing I have been wondering about: When I remove the base templates of has_member and has_function (which only contain a static_assert ), has_member always evaluates to false and has_function does not compile anymore and complains that free_func does not accept 0 arguments. I assume that the template arguments are not correctly bound to Ret and Args when using the function signature syntax, but I do not completely understand it. So any explanation here would also be appreciated.

My question now is: What am I doing wrong while trying to check if a free function with a given name and signature exists?

First - you're not checking for signatures anywhere here. You're checking to see if a call expression given a particular argument list produces a specific result:

struct X {
    int func(int);
};
static_assert(has_member<X, int(char)>::value, "!"); // doesn't fire

That said, there's a big difference between your check for member function and your check for free function, and that is which template parameters are in the immediate context of the substitution. In the member function case, T is a function template parameter that we try to substitute into this expression:

template<typename T, std::enable_if_t<
//       ~~~~~~~~~~
    std::is_same
    <
        decltype(std::declval<T>().func(std::declval<Args>()...)),
//               ~~~~~~~~~~~~~~~~~
        Ret   
    >::value 
> * = nullptr >
static constexpr std::true_type check(T*);

In the free function case, there is no function template parameter:

template<std::enable_if_t
<
    std::is_same
    <
        decltype(free_func(std::declval<Args>()...)),
        Ret    
    >::value
> * = nullptr >
static constexpr std::true_type check(nullptr_t);

This whole expression can be immediately substituted into at the point of instantiation. It doesn't depend on any of the parameters in check() , just the ones from has_function . Since free_func cannot be invoked with two double s, that substitution fails - but it's not in the immediate context of substituting in those local parameters, so it's a hard error. You need to change it to ensure that what you're substituting is relevant to the expression you're checking. That is, Args... :

template<typename Ret, typename... Args>
struct has_function<Ret(Args...)> {
private:
    template<typename... A, std::enable_if_t
//           ~~~~~~~~~~~~~
    <
        std::is_same
        <
            decltype(free_func(std::declval<A>()...)),
//                             ~~~~~~~~~~~~~~~~~
            Ret    
        >::value
    > * = nullptr >
    static constexpr std::true_type check(nullptr_t);

    template<typename...>
//           ~~~~~~~~~~~
    static constexpr std::false_type check(...);

    typedef decltype(check<Args...>(nullptr)) type;
//                   ~~~~~~~~~~~~~~
public:
    static constexpr bool value = type::value;
};

Note also that in order for this to work with non-class types as arguments, free_func has to be in scope at the point of definition of has_function .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM