How do I determine if a type is callable with only const references?

后端 未结 6 1812
清酒与你
清酒与你 2021-02-04 13:08

I want to write a C++ metafunction is_callable that defines value to be true, if and only if the type F has the function cal

相关标签:
6条回答
  • 2021-02-04 13:23

    Here's something I hacked up which may or may not be what you need; it does seem to give true (false) for (const) int &...

    #include <utility>
    
    template <typename F, typename Arg>
    struct Callable
    {
    private:
      typedef char                      yes;
      typedef struct { char array[2]; } no;
    
      template <typename G, typename Brg>
      static yes test(decltype(std::declval<G>()(std::declval<Brg>())) *);
    
      template <typename G, typename Brg>
      static no test(...);
    
    public:
      static bool const value = sizeof(test<F, Arg>(nullptr)) == sizeof(yes);
    };
    
    struct Foo
    {
      int operator()(int &) { return 1; }
      // int operator()(int const &) const { return 2; } // enable and compare
    };
    
    #include <iostream>
    int main()
    {
      std::cout << "Foo(const int &): " << Callable<Foo, int const &>::value << std::endl
                << "Foo(int &):       " << Callable<Foo, int &>::value << std::endl
        ;
    }
    
    0 讨论(0)
  • 2021-02-04 13:33

    (with apologies to Kerrek for using his answer as a starting point)

    EDIT: Updated to handle types without any operator() at all.

    #include <utility>
    
    template <typename F, typename Arg>
    struct Callable
    {
    private:
      static int tester[1];
      typedef char                      yes;
      typedef struct { char array[2]; } no;
    
      template <typename G, typename Brg>
      static char sfinae(decltype(std::declval<G>()(std::declval<Brg>())) (G::*pfn)(Brg)) { return 0; }
    
      template <typename G, typename Brg>
      static char sfinae(decltype(std::declval<G>()(std::declval<Brg>())) (G::*pfn)(Brg) const) { return 0; }
    
      template <typename G, typename Brg>
      static yes test(int (&a)[sizeof(sfinae<G,Brg>(&G::operator()))]);
    
      template <typename G, typename Brg>
      static no test(...);
    
    public:
      static bool const value = sizeof(test<F, Arg>(tester)) == sizeof(yes);
    };
    
    struct Foo
    {
      int operator()(int &) { return 1; }
    
    };
    
    struct Bar
    {
      int operator()(int const &) { return 2; }
    };
    
    struct Wazz
    {
      int operator()(int const &) const { return 3; }
    };
    
    struct Frob
    {
      int operator()(int &) { return 4; }
      int operator()(int const &) const { return 5; }
    };
    
    struct Blip
    {
      template<typename T>
      int operator()(T) { return 6; }
    };
    
    struct Boom
    {
    
    };
    
    struct Zap
    {
      int operator()(int) { return 42; }
    };
    
    #include <iostream>
    int main()
    {
      std::cout << "Foo(const int &):  " << Callable<Foo,  int const &>::value << std::endl
                << "Foo(int &):        " << Callable<Foo,  int &>::value << std::endl
                << "Bar(const int &):  " << Callable<Bar,  const int &>::value << std::endl
                << "Bar(int &):        " << Callable<Bar,  int &>::value << std::endl
                << "Zap(const int &):  " << Callable<Zap , const int &>::value << std::endl
                << "Zap(int&):         " << Callable<Zap , int &>::value << std::endl
                << "Wazz(const int &): " << Callable<Wazz, const int &>::value << std::endl
                << "Wazz(int &):       " << Callable<Wazz, int &>::value << std::endl
                << "Frob(const int &): " << Callable<Frob, const int &>::value << std::endl
                << "Frob(int &):       " << Callable<Frob, int &>::value << std::endl
                << "Blip(const int &): " << Callable<Blip, const int &>::value << std::endl
                << "Blip(int &):       " << Callable<Blip, int &>::value << std::endl
                << "Boom(const int &): " << Callable<Boom, const int &>::value << std::endl
                << "Boom(int&):        " << Callable<Boom, int &>::value << std::endl;
    }
    

    Demo: http://ideone.com/T3Iry

    0 讨论(0)
  • 2021-02-04 13:39

    Here is a possible solution that utilizes an extra test to see if your template is being instantiated with a const T&:

    #include <memory>
    #include <iostream>
    
    using namespace std;
    
    template<typename F, typename Arg>
    struct is_callable {
    private:
    
      template<typename>
      static char (&test(...))[2];
    
      template<bool, unsigned value>
      struct helper {};
    
      template<unsigned value>
      struct helper<true, value> {
        typedef void *type;
      };
    
      template<typename T>
      struct is_const_ref {};
    
      template<typename T>
      struct is_const_ref<T&> {
        static const bool value = false;
      };
    
      template<typename T>
      struct is_const_ref<const T&> {
        static const bool value = true;
      };
    
      template<typename UVisitor>
      static char test(typename helper<is_const_ref<Arg>::value, 
                                       sizeof(std::declval<UVisitor>()(std::declval<Arg>()), 0)>::type);
    public:
      static const bool value = (sizeof(test<F>(0)) == sizeof(char));
    };
    
    struct foo {
      void operator()(const int &) {}
    };
    
    int main(void)
    {
      cout << is_callable<foo, int &>::value << "\n";
      cout << is_callable<foo, const int &>::value << "\n";
    
      return 0;
    }
    
    0 讨论(0)
  • 2021-02-04 13:42

    Something like this maybe? It's a bit round about to make it work on VS2010.

    template<typename FPtr>
    struct function_traits_impl;
    
    template<typename R, typename A1>
    struct function_traits_impl<R (*)(A1)>
    {
        typedef A1 arg1_type;
    };
    
    template<typename R, typename C, typename A1>
    struct function_traits_impl<R (C::*)(A1)>
    {
        typedef A1 arg1_type;
    };
    
    template<typename R, typename C, typename A1>
    struct function_traits_impl<R (C::*)(A1) const>
    {
        typedef A1 arg1_type;
    };
    
    template<typename T>
    typename function_traits_impl<T>::arg1_type arg1_type_helper(T);
    
    template<typename F>
    struct function_traits
    {
        typedef decltype(arg1_type_helper(&F::operator())) arg1_type;
    };
    
    template<typename F, typename Arg>
    struct is_callable : public std::is_same<typename function_traits<F>::arg1_type, const Arg&>
    {
    }
    
    0 讨论(0)
  • 2021-02-04 13:42

    Ran across this while doing something else, was able to adapt my code to fit. It has the same features (and limitations) as @Xeo, but does not need sizeof trick/enable_if. The default parameter takes the place of needing to do the enable_if to handle template functions. I tested it under g++ 4.7 and clang 3.2 using the same test code Xeo wrote up

    #include <type_traits>
    #include <functional>
    
    namespace detail {
      template<typename T, class Args, class Enable=void>
      struct call_exact : std::false_type {};
    
      template<class...Args> struct ARGS { typedef void type; };
    
      template<class T, class ... Args, class C=T>
      C * opclass(decltype(std::declval<T>()(std::declval<Args>()...)) (C::*)(Args...)) { }
      template<class T, class ... Args, class C=T>
      C * opclass(decltype(std::declval<T>()(std::declval<Args>()...)) (C::*)(Args...) const) { }
    
      template<typename T, class ... Args>
      struct call_exact<T, ARGS<Args...>,
        typename ARGS<
           decltype(std::declval<T&>()(std::declval<Args>()...)),
           decltype(opclass<T, Args...>(&T::operator()))
         >::type
       > : std::true_type {};
    }
    
    template<class T, class ... Args>
    struct Callable : detail::call_exact<T, detail::ARGS<Args...>> { };
    
    template<typename R, typename... FArgs, typename... Args>
    struct Callable<R(*)(FArgs...), Args...>
     : Callable<std::function<R(FArgs...)>, Args...>{};
    
    0 讨论(0)
  • 2021-02-04 13:44

    After hours of playing around and some serious discussions in the C++ chat room, we finally got a version that works for functors with possibly overloaded or inherited operator() and for function pointers, based on @KerrekSB's and @BenVoigt's versions.

    #include <utility>
    #include <type_traits>
    
    template <typename F, typename... Args>
    class Callable{
      static int tester[1];
      typedef char yes;
      typedef yes (&no)[2];
    
      template <typename G, typename... Brgs, typename C>
      static typename std::enable_if<!std::is_same<G,C>::value, char>::type
          sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...));
    
      template <typename G, typename... Brgs, typename C>
      static typename std::enable_if<!std::is_same<G,C>::value, char>::type
          sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (C::*pfn)(Brgs...) const);
    
      template <typename G, typename... Brgs>
      static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...));
    
      template <typename G, typename... Brgs>
      static char sfinae(decltype(std::declval<G>()(std::declval<Brgs>()...)) (G::*pfn)(Brgs...) const);
    
      template <typename G, typename... Brgs>
      static yes test(int (&a)[sizeof(sfinae<G,Brgs...>(&G::operator()))]);
    
      template <typename G, typename... Brgs>
      static no test(...);
    
    public:
      static bool const value = sizeof(test<F, Args...>(tester)) == sizeof(yes);
    };
    
    template<class R, class... Args>
    struct Helper{ R operator()(Args...); };
     
    template<typename R, typename... FArgs, typename... Args>
    class Callable<R(*)(FArgs...), Args...>
      : public Callable<Helper<R, FArgs...>, Args...>{};
    

    Live example on Ideone. Note that the two failing tests are overloaded operator() tests. This is a GCC bug with variadic templates, already fixed in GCC 4.7. Clang 3.1 also reports all tests as passed.

    If you want operator() with default arguments to fail, there is a possible way to do that, however some other tests will start failing at that point and I found it as too much hassle to try and correct that.

    Edit: As @Johannes correctly notes in the comment, we got a little inconsistency in here, namely that functors which define a conversion to function pointer will not be detected as "callable". This is, imho, pretty non-trivial to fix, as such I won't bother with it (for now). If you absolutely need this trait, well, leave a comment and I'll see what I can do.


    Now that all this has been said, IMHO, the idea for this trait is stupid. Why whould you have such exact requirements? Why would the standard is_callable not suffice?

    (Yes, I think the idea is stupid. Yes, I still went and built this. Yes, it was fun, very much so. No, I'm not insane. Atleast that's what I believe...)

    0 讨论(0)
提交回复
热议问题