How to detect the presence of a static member function with certain signature?
Asked Answered
P

4

3

I found several questions & answers on SO dealing with detecting at compile time (via SFINAE) whether a given class has a member of certain name, type, or signature. However, I couldn't find one that also applies to static public member functions (when pointer-to-member tricks won't work). Any ideas?

Polyclitus answered 17/4, 2014 at 12:53 Comment(3)
What do you want to do with private methods? I assume you want a given signature?Seaver
Yes, I know, and that is a problem. class foo { private: static bool bar(); }; do you want to say 'foo has a static method bar' or not?Seaver
@Yakk Okay, now I understand. No, I don't want non-public static members. I edited the question to reflect that (the answers were already correct in this sense: they didn't detect non-public members).Polyclitus
C
8

Following may help: (https://ideone.com/nDlFUE)

#include <cstdint>

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

Then define a traits:

DEFINE_HAS_SIGNATURE(has_foo, T::foo, void (*)(void));
Cumbrous answered 17/4, 2014 at 12:57 Comment(2)
When I try to use this with gcc 4.8.4, I get: error: declaration of ‘class T’ template<typename T, T> struct helper;Outface
@DanielMoodie: Work as expected with 4.8.1 and 4.8.5. (cannot test with 4.8.4).Cumbrous
E
6

Here's one way:

#include <type_traits>

template<typename, typename>
struct has_fun;

template<typename T, typename Ret, typename... Args>
struct has_fun<T, Ret(Args...)> {
    template<typename U, U> struct Check;

    template<typename U>
    static std::true_type Test(Check<Ret(*)(Args...), &U::fun>*);

    template<typename U>
    static std::false_type Test(...);

    static const bool value = decltype(Test<T>(0))::value;
};

It's written for a function called fun. Use it like has_fun<T, int(int, int)>::value.

Here's another:

#include <type_traits>

template<typename, typename>
struct has_fun;

template<typename T, typename Ret, typename... Args>
struct has_fun<T, Ret(Args...)> {

    struct No {}; // need a unique type for the second overload
                  // so it doesn't match Ret and give a false positive
    template<typename U>
    static auto Test(int) -> decltype( U::fun(std::declval<Args>()...) );

    template<typename U>
    static No Test(...);

    static const bool value =
        std::is_same<decltype(Test<U>(0)), Ret>{};
};

It might be sensible to test whether the return type of the function is convertible to Ret instead of checking for exatch match. Use is_convertible instead of is_same in that case and, at the same time, check that return type is different than No (as Yakk points out, there are types out there that can be constructed from just about anything).

Enlargement answered 17/4, 2014 at 13:2 Comment(7)
You're welcome. I wrote it. I haven't tested it, so there could be errors.Enlargement
this actually only detects public static member, exactly what I wanted!Polyclitus
Does conversion on arguements but not return value, and why the && it does nothing?Seaver
@Yakk Right, && is a habit, not needed. The conversion on return type - that's probably the right thing to do, but up to OP. Thanks for the heads up.Enlargement
That does bring up another amusing corner case: std::function<whatever> is_convertible from your has_fun<whatever>::No.Seaver
@Yakk using R = decltype(Test<U>(0)); value = !is_same<R, No>{} && is_convertible<R, Ret>{}; should avoid that, right?Enlargement
"Edits must be at least 6 characters; is there something else to improve in this post?" The second method, last line Test<U>(0)) should be Test<T>(0))Ozonosphere
S
5

Simply invoke the member function and discard the result in a SFINAE context. If it succeeds, the method exists. If it fails, the method does not.

// not needed in C++1y
template<class T,class V=void>using enable_if_t=typename enable_if<T,V>::type;

// If the other tests fail, the type T does not have a method `foo` of
// signature Sig.  The class=void parameter is an implementation detail
// that in an industrial quality implementation we would hide in a helper
// template type.
template<class T,class Sig,class=void>struct has_foo:std::false_type{};

// For R(Args...), we attempt to invoke `T::foo` with (Args...), then check
// if we can assign the return value to a variable of type R.
template<class T,class R,class...Args>
struct has_foo<T,R(Args...),
  enable_if_t< // std:: in C++1y
    std::is_convertible<
      decltype( T::foo( std::declval<Args>()... ) ),
      R
    >::value
    && !std::is_same<R, void>::value
  >
>: std::true_type {};
// for `void` return value, we only care if the function can be invoked,
// no convertible test required:
template<class T,class...Args>
struct has_foo<T,void(Args...),
  decltype( void(T::foo( std::declval<Args>()... ) ) )
>: std::true_type {};

use:

 has_foo< bar, int(int) >::value

which checks if int r = T::foo( 7 ) is a valid expression, and not for exact signature match.

Seaver answered 17/4, 2014 at 14:4 Comment(7)
That's an ugly piece of code you've got there. +1 still :)Enlargement
@Enlargement price of phone coding. Fixed a typo or two and made it prettier by a touch.Seaver
There is an "ambiguous partial template specialization" when foo's return type is void (both partial specialization match) - I added a !std::is_same<R, void>::value inside the std::enable_if_t to correct this, but I was wondering if there was a better way?Berlyn
@holt What version of compiler and C++? void is not convertible to void last time I read the C++ standard definition of is_convertible. In addition, R=void should be less specialized than a hard-coded void. Your fix should be good enough, I'm just curious what compiler needs it. (I thought is_convertible<void,void> required regular void, which is proposed for C++20).Seaver
@Yakk I tested with clang and gcc, using different versions, and I always get the same result: godbolt.org/g/BqLtU2, perhaps I missed something... I only tried for c++14 (-std=c++14).Berlyn
@Yakk I think the problem is that the template is specialized for the target type (in which case void is more specialized than R) but not for the target function.Berlyn
@holt My in-head compiler gets things wrong sometimes. Ya, throw the R is not void requirement in there. I guess claiming void is convertible to void is a common thing for compilers to say.Seaver
C
0

Here is demo code (similar to another code snippet in here), except simplified and supplied with example code.

#include <iostream>

#define DEFINE_FUNCTION_CHECKER(traitsName, funcName, signature)                                        \
template <typename U> class traitsName                                                                  \
{                                                                                                       \
    template<typename T, T> struct helper;                                                              \
    template<typename T> static std::true_type check(helper<signature, T::funcName>*);                  \
    template<typename T> static std::false_type check(...);                                             \
public:                                                                                                 \
    static constexpr bool value = decltype(check<U>(0))::value;                                         \
};

template <class T>
class Factory
{
public:
    Factory()
    {
        std::cout << "Factory of " << typeid(T).name() << std::endl;
    }
};

class A
{
public:
    static void RegisterFactory()
    {
        static Factory<A> factory;
    }
};

class B
{
public:
    static Factory<B> factory;
};

DEFINE_FUNCTION_CHECKER(has_RegisterFactory, RegisterFactory, void (*)(void))

template <class T>
void func()
{
    if constexpr (has_RegisterFactory<T>::value)
        T::RegisterFactory();
}

int main()
{
    func<A>();
    func<B>();
}

Code will print Factory of class A, even thus there exists static class factory for B class as well, but it's unused (B's factory constructor never called).

Casmey answered 26/10, 2023 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.