【问题标题】:How do I determine if a type is callable with only const references?如何确定一个类型是否可以仅使用 const 引用来调用?
【发布时间】:2012-01-18 01:15:28
【问题描述】:

我想编写一个 C++ 元函数 is_callable<F, Arg>,它将 value 定义为 true,当且仅当 F 类型具有 SomeReturnType operator()(const Arg &) 形式的函数调用运算符时。比如下面这种情况

struct foo {
  void operator(const int &) {}
};

我希望is_callable<foo, int &> 成为falseis_callable<foo, const int &> 成为true。这是我到目前为止所拥有的:

#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;
}

这打印出11,但我想要01,因为foo 只定义void operator()(const int &amp;)

【问题讨论】:

  • 我有一个想法,使用is_constructible 以及一个包含decltype(F()(Arg())) 类型成员的辅助类,但它的参与程度比我刚才所允许的注意力范围要多。我认为如果整个班级都参加 SFINAE 测试,它可能会奏效。
  • 我也对这个问题的解决方案感兴趣,特别是可以处理foo::operator() 过载和/或模板的情况。
  • decltype(F()(Arg())) 将无法按预期工作,因为 Arg 将再次从 const int 转换为 int
  • “我希望 is_callable 为假, is_callable 为真。”但这不会是谎言吗?您可以将非常量引用传递给采用const&amp; 的函数。据推测,如果您可以使用给定参数调用函数,而不是函数直接采用该参数的值,则您希望is_callable 为真。是吗?
  • @Nicol:我同意is_callable 不是这样一个特征的正确名称,但是,它是一个有趣的设计特征。

标签: c++ templates c++11 template-meta-programming sfinae


【解决方案1】:

在玩了几个小时和一些serious discussions in the C++ chat room 之后,我们终于得到了一个版本,该版本适用于可能重载或继承operator() 的函子和函数指针,基于@KerrekSB 和@BenVoigt 的版本。

#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。请注意,两个失败的测试是重载的operator() 测试。这是一个带有可变参数模板的 GCC 错误,已在 GCC 4.7 中修复。 Clang 3.1 还将所有测试报告为passed

如果您希望带有默认参数的 operator() 失败,有一种可能的方法可以做到这一点,但是此时其他一些测试将开始失败,我发现尝试纠正它太麻烦了。

编辑:正如@Johannes 在评论中正确指出的那样,我们在这里有一点不一致,即functors which define a conversion to function pointer 不会被检测为“可调用”。这是,恕我直言,修复起来非常重要,因此我不会打扰它(现在)。如果你绝对需要这个特性,那么,留下评论,我会看看我能做什么。


既然已经说了这么多,恕我直言,这个特性的想法是愚蠢的。为什么你有这样确切的要求?为什么标准的is_callable 不够用?

(是的,我认为这个想法很愚蠢。是的,我还是去建造了这个。是的,这很有趣,非常有趣。不,我没有疯。至少我是这么认为的......)

【讨论】:

  • 函数指针没有 operator() 所以他们应该根据问题报告它们不可调用。否则,如果您想捕获任何可调用类型,您还需要处理具有到函数指针或引用类型的转换函数的类类型。
  • @Johannes:第一部分相当简单,只需让Callable 的部分规范有一个static bool const value = false; 成员,而不是从其自身继承。第二部分......是一个很好的观点,可能会有点复杂,所以我现在不打算打扰它。 :( 这个答案也缺少对 volatile 或 ref-qualifed 成员函数的测试,但这些 相当容易 添加,尽管它在 sfinae 重载的数量上变得非常混乱。跨度>
  • @Xeo :快速提问......sfinae 的三模板参数版本何时实例化?我在 ideone 上查看了您的示例并注释掉了那些模板函数,并得到了相同的结果。
  • @Jason:这很有趣。从理论上讲,它们适用于Inh 的情况,其中operator() 是继承的,因为在这种情况下,成员函数指针的类类型与您要测试的类类型不同(&amp;Inh::operator() 是类型int (Bar::*)(int const&amp;))。在我添加那些推断指针上的类类型的重载之前,我在那里得到了一个 FAILED。让我重新检查...
  • @Jason:哎呀,发现了错误。这是最后一个测试用例的复制粘贴问题。我可能写过Inh 将被测试,但实际上Bar 被测试了,这就是原因。我非常担心自己的神志不清。在最后一行this is the outcome 中将test&lt;Bar&gt; 替换为test&lt;Inh&gt; 后,正确报告FAILED。我会编辑答案。
【解决方案2】:

(对 Kerrek 以他的回答为出发点表示歉意)

编辑:更新为处理没有任何 operator() 的类型。

#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;
}

演示:http://ideone.com/T3Iry

【讨论】:

  • 有趣的是,它也适用于operator()(int):ideone.com/egvJl。干得好,+1。 :)
  • 我知道如何解决这个问题,请稍等。
  • @BenVoigt:这是原始海报设计工作良好的一个领域......如果operator() 根本不存在,它会优雅地失败并给出错误值。
  • 有趣的是,当使用variadic templates 时,GCC 4.5.1 会针对重载情况报告0。然而,使用 Clang 3.1 进行的测试确认一切都很好,报告 1 用于过载情况。 FWIW,我得到了一个版本,works with function pointers。请注意此处可变参数版本中的重载问题。它适用于 Clang 3.1。
  • @Xeo:你应该这样回答,它比我的强大得多。
【解决方案3】:

这是我破解的一些东西,可能是也可能不是你需要的;对于(const) int &amp;,它似乎确实给出了真(假)...

#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
    ;
}

【讨论】:

  • 有趣的解决方案。问题是,如果 Foo 看起来像这样,struct Foo { int operator()(const int &amp;) const { return 2; } };,那么Callable&lt;Foo, int &amp;&gt;::value 是 1。我预计是 0。
【解决方案4】:

可能是这样的?让它在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&>
{
}

【讨论】:

  • 如果类有多个 operator() 重载,则不起作用。
【解决方案5】:

这是一个可能的解决方案,它利用额外的测试来查看您的模板是否正在使用 const T&amp; 进行实例化:

#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;
}

【讨论】:

  • 但是你需要一个 volatile 的特殊情况。还有一个volatile const
  • 这取决于传入函子中的operator() 声明,我想...
【解决方案6】:

在做其他事情时遇到了这个问题,能够调整我的代码以适应。它具有与@Xeo 相同的功能(和限制),但不需要 sizeof 技巧/enable_if。默认参数代替了需要执行 enable_if 来处理模板函数。我使用 Xeo 编写的相同测试代码在 g++ 4.7 和 clang 3.2 下对其进行了测试

#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...>{};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-05-30
    • 2018-08-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多