mbed_dev
mbed_dev

Reputation: 1470

Check if templated member function exists SFINAE

Following issue: I want to check whether a templated method is existing or not, herefore I have adapted the examples given here: Is it possible to write a template to check for a function's existence?

#include <cstdio>
#include <type_traits>

#define CHECK4_MEMBER_FUNC(RETTYPE,FUNCTION,...) \
template <class ClassType> \
class CIfCheck_##FUNCTION \
{\
private: \
    template <class MemberPointerType> \
    static std::true_type testSignature(RETTYPE (MemberPointerType::*)(__VA_ARGS__)); \
\
    template <class MemberPointerType> \
    static std::false_type testExistence(...); \
   \
    template <class MemberPointerType> \
    static decltype(testSignature(&MemberPointerType::FUNCTION)) testExistence(std::nullptr_t); \
public: \
    using type = decltype(testExistence<ClassType>(nullptr));\
    static const bool value = type::value; \
};


    class Bla
    {
    public:
        template <typename SomeType>
        bool init(SomeType someValue)
        {
            ///
            return true;
        }

        void exec()
        {
            return;
        }
    };

    CHECK4_MEMBER_FUNC(bool, init, int);
    CHECK4_MEMBER_FUNC(void, exec, void);

int main()
{
  Bla blaObj;
  blaObj.init<int>(2);
  static_assert(CIfCheck_exec<Bla>::value, "no exec");
  static_assert(CIfCheck_init<Bla>::value, "no init");

  return 0;
}

but unfortunately the static_assert() is triggered for init() (as the specialization is probably evaluated at a later time as the object is being instantiated in main()) .

I tried with explicit member-specialization, but it is still failing:

template<>
bool Bla::init<int>(int item)
{
    int temp = item*2; // do with item something
    return false;
}

P.S.: side-question (probably another question topic would make more sense:

std::false_type testExistence(...);

Why exactly do I have to pass an argument here? If I remove the variadic argument ... option (and nullptr and nullptr_t), the compiler errors due to ambiguous existence of testExistence().

Upvotes: 0

Views: 283

Answers (1)

max66
max66

Reputation: 66200

but unfortunately the static_assert is triggered for init (as the specialization is probably evaluated at a later time as the object is being instantiated in main())

Not exactly.

The problem is that init() is a template method, so when you write

decltype(testSignature(&MemberPointerType::FUNCTION))

no pointer is selected because the compiler can't select the right method.

You can try with

decltype(testSignature(&MemberPointerType::template FUNCTION<__VA_ARGS__>))

but now doesn't works for exec() that isn't a template method

To works with both template and non-template method... not simple passing trough a variadic macro because the variadic part can't be empty... but I propose something as follows

template <typename...>
struct wrap
 { };

#define CHECK4_MEMBER_FUNC(RETTYPE,FUN,...) \
template <class ClassType> \
class CIfCheck_##FUN \
{\
private: \
    template <typename MPT> \
    static auto testSig (wrap<void>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN()),\
                       RETTYPE>; \
    \
    template <typename MPT, typename ... As> \
    static auto testSig (wrap<As...>) \
       -> std::is_same<decltype(std::declval<MPT>().FUN(std::declval<As>()...)), \
                       RETTYPE>; \
    \
    template <typename...> \
    static std::false_type testSig (...);\
    \
public: \
    using type = decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));\
    static const bool value = type::value; \
};

Observe that I've added a wrap structure to wrap template parameter types; usually is used std::tuple but, in this case, we need wrap<void> because std::tuple<void> gives error.

Observe also that my solution is different from another point of view (and can be better or worse, according your specific needs): your solution check if is present a method with the exactly signature; my solution check if is present a method that is callable with a given list of arguments.

Concrete example: suppose that there is a Bla::foo() method that accept a long value

    void foo (long)
     { }

With your solution, if you check for an int parameter

CHECK4_MEMBER_FUNC(void, foo, int);

static_assert( false == CIfCheck_foo<Bla>::value, "no foo with int");

you get a false value from CIfCheck_foo because there isn't in Bla a method foo of type void(&BLA::*)(int) (there is a void(&BLA::*)(long) that is different).

With my method you get a true value from CIfCheck_foo because the foo(long) accept also a int value (and the returned type is void).


std::false_type testExistence(...);

Why exactly do I have to pass an argument here? If I remove the variadic argument ... option (and nullptr and nullptr_t), the compiler errors due to ambiguous existence of testExistence().

That testExistence(), as

    template <typename...> \
    static std::false_type testSig (...);\

is the second choice.

I mean... when you macro call testExistence() inside decltype()

decltype(testExistence<ClassType>(nullptr));

or my macro call testSig() inside decltype()

decltype(testSig<ClassType>(wrap<__VA_ARGS__>{}));

call that functions with an argument (nullptr or wrap<__VA_ARGS__>{}).

When the first choice is available (when is present a RETTYPE (MemberPointerType::*)(__VA_ARGS__) in your case, when is callable a method with the required arguments in my example), the compiler choose that version and return std::true_type (or std::is_same in my code).

But when the first choice isn't available?

Second choices, the versions returning std::false, are required. But the call is with an argument. The ellipsis here is the old C-style variadic argument list and accept zero or more arguments, so accept also one argument.

If you remove the ellipsis (...), the second choices can't accept an argument anymore (become zero arguments functions) and you get a compilation error because the compiler doesn't find a second choice function compatible with an argument.

Upvotes: 1

Related Questions