I have written some template helpers to check if a class has a certain method with a certain signature. The code is based on this answer. I attempted to extend it for generic methods. An important feature is that it also detects inherited methods.
#include <type_traits>
#include <string>
#include <iostream>
template <typename, typename, typename T>
struct has_method {
static_assert(std::integral_constant<T, false>::value,
"Third template parameter needs to be of function type.");
};
template <typename C, class caller, typename Ret, typename... Args>
struct has_method<C, caller, Ret(Args...)> {
private:
template <typename T>
static constexpr auto check(T *) ->
typename std::is_same<decltype(std::declval<caller>().template call<T>(
std::declval<Args>()...)),
Ret>::type
{
return typename std::is_same<
decltype(std::declval<caller>().template call<T>(
std::declval<Args>()...)),
Ret>::type();
//return to surpresswarnings
}
template <typename>
static constexpr std::false_type check(...)
{
return std::false_type();
};
typedef decltype(check<C>(0)) type;
public:
static constexpr bool value = type::value;
};
struct existent_caller {
template <class T, typename... Args>
constexpr auto call(Args... args) const
-> decltype(std::declval<T>().existent(args...))
{
return decltype(std::declval<T>().existent(args...))();
//return to surpresswarnings
}
};
struct nonexsistent_caller {
template <class T, typename... Args>
constexpr auto call(Args... args) const
-> decltype(std::declval<T>().nonexsistent(args...));
};
struct X {
int existent(const std::string &) { return 42; }
};
struct Y : X {
};
struct Z {
};
int main(int argc, const char *argv[])
{
static_assert(
has_method<X, existent_caller, int(const std::string &)>::value,
"Should have existent method");
static_assert(
has_method<Y, existent_caller, int(const std::string &)>::value,
"Should have existent method");
static_assert(
!has_method<Z, existent_caller, int(const std::string &)>::value,
"Should not have existent method");
static_assert(
!has_method<X, nonexsistent_caller, int(const std::string &)>::value,
"Should not have nonexistent method");
static_assert(
!has_method<Y, nonexsistent_caller, int(const std::string &)>::value,
"Should not have nonexistent method");
static_assert(
!has_method<Z, nonexsistent_caller, int(const std::string &)>::value,
"Should not have nonexistent method");
static_assert(
!has_method<X, existent_caller, double(const std::string &)>::value,
"Should have wrong signature");
static_assert(
!has_method<Y, existent_caller, double(const std::string &)>::value,
"Should have wrong signature");
static_assert(
!has_method<Z, existent_caller, double(const std::string &)>::value,
"Should not have method");
static_assert(!has_method<X, existent_caller, int(double)>::value,
"Should have wrong signature");
static_assert(!has_method<Y, existent_caller, int(double)>::value,
"Should have wrong signature");
static_assert(!has_method<Z, existent_caller, int(double)>::value,
"Should not have method");
std::cout << has_method<Y, existent_caller, int(const std::string &)>::value
<< "\n"; // will print 1
std::cout << has_method<Z, existent_caller, int(const std::string &)>::value
<< "\n"; // will print 0
std::cout
<< has_method<Y, nonexsistent_caller, int(const std::string &)>::value
<< "\n"; // will print 0
std::cout
<< has_method<Z, nonexsistent_caller, int(const std::string &)>::value
<< "\n"; // will print 0
return 0;
}
(the cout
s are only for assurance)
So far it works, but my concern is the way the callers are defined. The decltype
stuff is not easy to read. Does someone have ideas on how to give it a cleaner "interface" (or however you would call it)?
-
2\$\begingroup\$ I won't make an answer out of this, but a function detection toolkit has been proposed for inclusion into the standard library. You could use some of the ideas exploited in there. \$\endgroup\$Morwenn– Morwenn2015年06月08日 15:44:02 +00:00Commented Jun 8, 2015 at 15:44
-
\$\begingroup\$ Please do not change the code once answered, that's against the site policies for it may invalidate the answers. Please see what you may and may not do after receiving answers. \$\endgroup\$Morwenn– Morwenn2015年06月11日 14:45:42 +00:00Commented Jun 11, 2015 at 14:45
-
\$\begingroup\$ Ok. I wasn't aware of this. \$\endgroup\$MagunRa– MagunRa2015年06月11日 14:48:59 +00:00Commented Jun 11, 2015 at 14:48
1 Answer 1
Compiler warnings
I compiled your code with -Wall -Wextra -pedantic
and they managed to trigger some warnings:
You have an extra comma at the end of the function
static constexpr std::false_type check(...)
. Get rid of it.You're not using
argc
norargv
. You might want to simply useint main()
if you're not going to use the parameters.
Always request more warnings, never ignore them. -- Morwenn, 2015年06月11日
That quote was totally unoriginal. -- Morwenn, same day
Other miscellaneous stuff
There isn't much to say, so I will only nitpick as I usually do :)
You don't need the methods
check
to have a definition since you only use them for their signature. Remove their body and let them undefined, it will make it clearer why they exist. This also holds forexistent_caller<>::call
.Alternatively, you can use the list initialization syntax instead if not providing the body triggers some warnings so that you don't have to repeat the return type:
static constexpr auto check(T *) -> typename std::is_same<decltype(std::declval<caller>().template call<T>( std::declval<Args>()...)), Ret>::type { // Default-construct an instance of the return type return {}; }
typedef
is a bit old-fashioned and subjectively not that much readable. You could use the new type alias withusing
instead:using type = decltype(check<C>(0));
By the way,
check
takes a pointer, make it clear by passingnullptr
instead of0
:using type = decltype(check<C>(nullptr));
Your methods in
existentcaller
andnonexistentcaller
could bestatic
. It seems that you don't need them to be regular methods.Also, you don't need to
return 0;
at the end ofmain
. If the compiler reaches the end ofmain
without finding areturn
statement, it automagically return 0. Removingreturn 0;
is a way to document that your program can't return anything else than 0 from and that it should never return error codes.
-
\$\begingroup\$ Hi I guess you compile with gcc? If I leave out the return clang++ gives me a lot of warnings that the functions are not defined. \$\endgroup\$MagunRa– MagunRa2015年06月11日 14:27:07 +00:00Commented Jun 11, 2015 at 14:27
-
\$\begingroup\$ @MagunRa I tried both GCC and Clang thanks to Coliru :) \$\endgroup\$Morwenn– Morwenn2015年06月11日 14:27:32 +00:00Commented Jun 11, 2015 at 14:27
-
\$\begingroup\$ I have changed it but clang++ gives me a lot of warnings like this:
warning: inline function 'existent_caller::call<X, std::basic_string<char> >' is not defined [-Wundefined-inline]
\$\endgroup\$MagunRa– MagunRa2015年06月11日 14:40:05 +00:00Commented Jun 11, 2015 at 14:40 -
\$\begingroup\$ @MagunRa I included another method that shouldn't give you warnings anymore (and tested with both compilers) :) \$\endgroup\$Morwenn– Morwenn2015年06月12日 08:59:17 +00:00Commented Jun 12, 2015 at 8:59
-
\$\begingroup\$ Nice. This does get rid off the warnings. \$\endgroup\$MagunRa– MagunRa2015年06月12日 12:33:18 +00:00Commented Jun 12, 2015 at 12:33
Explore related questions
See similar questions with these tags.