Reputation: 1470
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
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 (andnullptr
andnullptr_t
), the compiler errors due to ambiguous existence oftestExistence()
.
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