5
\$\begingroup\$

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 couts 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)?

asked Jun 8, 2015 at 13:21
\$\endgroup\$
3
  • 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\$ Commented 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\$ Commented Jun 11, 2015 at 14:45
  • \$\begingroup\$ Ok. I wasn't aware of this. \$\endgroup\$ Commented Jun 11, 2015 at 14:48

1 Answer 1

2
\$\begingroup\$

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 nor argv. You might want to simply use int 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 for existent_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 with using instead:

    using type = decltype(check<C>(0));
    
  • By the way, check takes a pointer, make it clear by passing nullptr instead of 0:

    using type = decltype(check<C>(nullptr));
    
  • Your methods in existentcaller and nonexistentcaller could be static. It seems that you don't need them to be regular methods.

  • Also, you don't need to return 0; at the end of main. If the compiler reaches the end of main without finding a return statement, it automagically return 0. Removing return 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.

answered Jun 11, 2015 at 14:10
\$\endgroup\$
5
  • \$\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\$ Commented Jun 11, 2015 at 14:27
  • \$\begingroup\$ @MagunRa I tried both GCC and Clang thanks to Coliru :) \$\endgroup\$ Commented 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\$ Commented 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\$ Commented Jun 12, 2015 at 8:59
  • \$\begingroup\$ Nice. This does get rid off the warnings. \$\endgroup\$ Commented Jun 12, 2015 at 12:33

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.