I\'m asking for a template trick to detect if a class has a specific member function of a given signature.
The problem is similar to the one cited here http://www.go
Here are some usage snippets: *The guts for all this are farther down
Check for member x
in a given class. Could be var, func, class, union, or enum:
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
Check for member function void x()
:
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
Check for member variable x
:
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
Check for member class x
:
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
Check for member union x
:
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
Check for member enum x
:
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
Check for any member function x
regardless of signature:
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
OR
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
Details and core:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
Macros (El Diablo!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
I'm not sure if I understand you correctly, but you may exploit SFINAE to detect function presence at compile-time. Example from my code (tests if class has member function size_t used_memory() const).
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
The accepted answer to this question of compiletime member-function introspection, although it is justly popular, has a snag which can be observed in the following program:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
Built with GCC 4.6.3, the program outputs 110
- informing us that
T = std::shared_ptr<int>
does not provide int & T::operator*() const
.
If you are not already wise to this gotcha, then a look at of the definition of
std::shared_ptr<T>
in the header <memory>
will shed light. In that
implementation, std::shared_ptr<T>
is derived from a base class
from which it inherits operator*() const
. So the template instantiation
SFINAE<U, &U::operator*>
that constitutes "finding" the operator for
U = std::shared_ptr<T>
will not happen, because std::shared_ptr<T>
has no
operator*()
in its own right and template instantiation does not
"do inheritance".
This snag does not affect the well-known SFINAE approach, using "The sizeof() Trick",
for detecting merely whether T
has some member function mf
(see e.g.
this answer and comments). But
establishing that T::mf
exists is often (usually?) not good enough: you may
also need to establish that it has a desired signature. That is where the
illustrated technique scores. The pointerized variant of the desired signature
is inscribed in a parameter of a template type that must be satisfied by
&T::mf
for the SFINAE probe to succeed. But this template instantiating
technique gives the wrong answer when T::mf
is inherited.
A safe SFINAE technique for compiletime introspection of T::mf
must avoid the
use of &T::mf
within a template argument to instantiate a type upon which SFINAE
function template resolution depends. Instead, SFINAE template function
resolution can depend only upon exactly pertinent type declarations used
as argument types of the overloaded SFINAE probe function.
By way of an answer to the question that abides by this constraint I'll
illustrate for compiletime detection of E T::operator*() const
, for
arbitrary T
and E
. The same pattern will apply mutatis mutandis
to probe for any other member method signature.
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
In this solution, the overloaded SFINAE probe function test()
is "invoked
recursively". (Of course it isn't actually invoked at all; it merely has
the return types of hypothetical invocations resolved by the compiler.)
We need to probe for at least one and at most two points of information:
T::operator*()
exist at all? If not, we're done.T::operator*()
exists, is its signature
E T::operator*() const
?We get the answers by evaluating the return type of a single call
to test(0,0)
. That's done by:
typedef decltype(test<T>(0,0)) type;
This call might be resolved to the /* SFINAE operator-exists :) */
overload
of test()
, or it might resolve to the /* SFINAE game over :( */
overload.
It can't resolve to the /* SFINAE operator-has-correct-sig :) */
overload,
because that one expects just one argument and we are passing two.
Why are we passing two? Simply to force the resolution to exclude
/* SFINAE operator-has-correct-sig :) */
. The second argument has no other signifance.
This call to test(0,0)
will resolve to /* SFINAE operator-exists :) */
just
in case the first argument 0 satifies the first parameter type of that overload,
which is decltype(&A::operator*)
, with A = T
. 0 will satisfy that type
just in case T::operator*
exists.
Let's suppose the compiler say's Yes to that. Then it's going with
/* SFINAE operator-exists :) */
and it needs to determine the return type of
the function call, which in that case is decltype(test(&A::operator*))
-
the return type of yet another call to test()
.
This time, we're passing just one argument, &A::operator*
, which we now
know exists, or we wouldn't be here. A call to test(&A::operator*)
might
resolve either to /* SFINAE operator-has-correct-sig :) */
or again to
might resolve to /* SFINAE game over :( */
. The call will match
/* SFINAE operator-has-correct-sig :) */
just in case &A::operator*
satisfies
the single parameter type of that overload, which is E (A::*)() const
,
with A = T
.
The compiler will say Yes here if T::operator*
has that desired signature,
and then again has to evaluate the return type of the overload. No more
"recursions" now: it is std::true_type
.
If the compiler does not choose /* SFINAE operator-exists :) */
for the
call test(0,0)
or does not choose /* SFINAE operator-has-correct-sig :) */
for the call test(&A::operator*)
, then in either case it goes with
/* SFINAE game over :( */
and the final return type is std::false_type
.
Here is a test program that shows the template producing the expected answers in varied sample of cases (GCC 4.6.3 again).
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
Are there new flaws in this idea? Can it be made more generic without once again falling foul of the snag it avoids?
Building on jrok's answer, I have avoided using nested template classes and/or functions.
#include <type_traits>
#define CHECK_NESTED_FUNC(fName) \
template <typename, typename, typename = std::void_t<>> \
struct _has_##fName \
: public std::false_type {}; \
\
template <typename Class, typename Ret, typename... Args> \
struct _has_##fName<Class, Ret(Args...), \
std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
: public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
{}; \
\
template <typename Class, typename Signature> \
using has_##fName = _has_##fName<Class, Signature>;
#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value
We can use the above macros as below:
class Foo
{
public:
void Bar(int, const char *) {}
};
CHECK_NESTED_FUNC(Bar); // generate required metafunctions
int main()
{
using namespace std;
cout << boolalpha
<< HAS_NESTED_FUNC(Foo, Bar, void(int, const char *)) // prints true
<< endl;
return 0;
}
Suggestions are welcome.
To accomplish this we'll need to use:
true_type
overload expecting an int
and the false_type
overload expecting Variadic Parameters to exploit: "The lowest priority of the ellipsis conversion in overload resolution"true_type
function we will use declval and decltype allowing us to detect the function independent of return type differences or overloads between methodsYou can see a live example of this here. But I'll also explain it below:
I want to check for the existence of a function named test
which takes a type convertible from int
, then I'd need to declare these two functions:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
decltype(hasTest<a>(0))::value
is true
(Note there is no need to create special functionality to deal with the void a::test()
overload, the void a::test(int)
is accepted)decltype(hasTest<b>(0))::value
is true
(Because int
is convertable to double
int b::test(double)
is accepted, independent of return type)decltype(hasTest<c>(0))::value
is false
(c
does not have a method named test
that accepts a type convertible from int
therefor this is not accepted)This solution has 2 drawbacks:
test()
method?So it's important that these functions be declared in a details namespace, or ideally if they are only to be used with a class, they should be declared privately by that class. To that end I've written a macro to help you abstract this information:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
You could use this like:
namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
Subsequently calling details::test_int<a>::value
or details::test_void<a>::value
would yield true
or false
for the purposes of inline code or meta-programming.
Without C++11 support (decltype
) this might work:
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
A
, Aa
and B
are the clases in question, Aa
being the special one that inherits the member we're looking for.
In the FooFinder
the true_type
and false_type
are the replacements for the correspondent C++11 classes. Also for the understanding of template meta programming, they reveal the very basis of the SFINAE-sizeof-trick.
The TypeSink
is a template struct that is used later to sink the integral result of the sizeof
operator into a template instantiation to form a type.
The match
function is another SFINAE kind of template that is left without a generic counterpart. It can hence only be instantiated if the type of its argument matches the type it was specialized for.
Both the test
functions together with the enum declaration finally form the central SFINAE pattern. There is a generic one using an ellipsis that returns the false_type
and a counterpart with more specific arguments to take precedence.
To be able to instantiate the test
function with a template argument of T
, the match
function must be instantiated, as its return type is required to instantiate the TypeSink
argument. The caveat is that &U::foo
, being wrapped in a function argument, is not referred to from within a template argument specialization, so inherited member lookup still takes place.