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

\$\endgroup\$
  • 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 Jun 8 '15 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 Jun 11 '15 at 14:45
  • \$\begingroup\$ Ok. I wasn't aware of this. \$\endgroup\$ – MagunRa Jun 11 '15 at 14:48
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.

\$\endgroup\$
  • \$\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 Jun 11 '15 at 14:27
  • \$\begingroup\$ @MagunRa I tried both GCC and Clang thanks to Coliru :) \$\endgroup\$ – Morwenn Jun 11 '15 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 Jun 11 '15 at 14:40
  • \$\begingroup\$ @MagunRa I included another method that shouldn't give you warnings anymore (and tested with both compilers) :) \$\endgroup\$ – Morwenn Jun 12 '15 at 8:59
  • \$\begingroup\$ Nice. This does get rid off the warnings. \$\endgroup\$ – MagunRa Jun 12 '15 at 12:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.