可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to write a C++ metafunction is_callable<F, Arg>
that defines value
to be true
, if and only if the type F has the function call operator of the form SomeReturnType operator()(const Arg &)
. For example, in the following case
struct foo {
void operator(const int &) {}
};
I want is_callable<foo, int &>
to be false
and is_callable<foo, const int &>
to be true
. This is what I have so far :
#include <memory>
#include <iostream>
template<typename F, typename Arg>
struct is_callable {
private:
template<typename>
static char (&test(...))[2];
template<unsigned>
struct helper {
typedef void *type;
};
template<typename UVisitor>
static char test(
typename helper<
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 &) {}
};
using namespace std;
int main(void)
{
cout << is_callable<foo, int &>::value << "\n";
cout << is_callable<foo, const int &>::value << "\n";
return 0;
}
This prints 1
and 1
, but I want 0
and 1
because foo
only defines void operator()(const int &)
.
回答1:
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...)
回答2:
(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
回答3:
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
;
}
回答4:
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&>
{
}
回答5:
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;
}
回答6:
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...>{};