【问题标题】:Get argument type of template callable object获取模板可调用对象的参数类型
【发布时间】:2014-05-03 01:47:31
【问题描述】:

考虑以下函数:

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
}

这里的f 是一些可调用对象,接受一个参数。它可能是函数指针、std::functionstd::bind 的结果。

问题是,如何确定f的参数类型,并根据该类型做一些动作?


一个简单的解决方法是将类型显式添加到模板中,例如

template<class T, class F> // T is the argument type of F
void register_handler( F& f )

但这似乎有点矫枉过正,因为F 类型应该已经包含有关T 类型的必要信息。

【问题讨论】:

  • 您可以专门化模板以满足您的需求。我不记得确切的语法和确切的做法,但你可以看看:Template specialization 希望这会有所帮助:) 编辑:我找到了一个更好的链接
  • 您将这些信息用于什么目的?如下所述,您的问题通常无法解决,但引发问题的问题可能是可以解决的。
  • 请描述您的问题,而不是您的解决方案。 “获取参数类型f!”是您当前遇到的问题的解决方案吗?
  • 如果有帮助,“因为类型 F 应该已经包含关于类型 T 的必要信息。”是一个无效的假设。
  • RapidCheck 中可以找到一个很好的参考实现,尤其是here 注意这个实现只能处理实现的参数类型(大多数原始类型都实现了)。

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


【解决方案1】:

假设F 是任何可调用类型,您无法获取其参数类型。考虑一下:

struct callable
{
    void operator() (int);
    void operator() (float *);
    void operator() (std::string const &);
    void operator() (std::list<int> &);
};

参数的类型在这里是模棱两可的。

【讨论】:

  • 在 c++1y 中使用通用 lambda [](auto v) {}
  • @lisyarus 你可以定义一个作为默认值。使用 sfinae 时,如果发现不止一种可能性,这将是首选的重载。
  • @galop1n:泛型 lambda 只是 template &lt;typename T&gt; operator() 的糖,所以在 c++11 和 c++03 中也存在同样的问题。
  • @DavidFeurle:要选择默认重载,我应该得到可能的重载列表,这是不可能的。
  • 如果您要预先定义可能的签名,您可以只测试该类型是否可以使用这些签名调用,而忽略这个“获取参数类型”的废话。
【解决方案2】:

这个blogpost 展示了如何实现一些函数类型特征。这些应该适用于所有可调用的东西(例外:多态函子:P)。您可以遍历参数,并使用它们的类型来做一些 sfinae 或作为额外的模板参数。

blogpost复制的功能特征:

#include <tuple>

// as seen on http://functionalcpp.wordpress.com/2013/08/05/function-traits/
template<class F>
struct function_traits;

// function pointer
template<class R, class... Args>
struct function_traits<R(*)(Args...)> : public function_traits<R(Args...)>
{};

template<class R, class... Args>
struct function_traits<R(Args...)>
{
    using return_type = R;

    static constexpr std::size_t arity = sizeof...(Args);

    template <std::size_t N>
    struct argument
    {
        static_assert(N < arity, "error: invalid parameter index.");
        using type = typename std::tuple_element<N,std::tuple<Args...>>::type;
    };
};

// member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...)> : public function_traits<R(C&,Args...)>
{};

// const member function pointer
template<class C, class R, class... Args>
struct function_traits<R(C::*)(Args...) const> : public function_traits<R(C&,Args...)>
{};

// member object pointer
template<class C, class R>
struct function_traits<R(C::*)> : public function_traits<R(C&)>
{};

// functor
template<class F>
struct function_traits
{
    private:
        using call_type = function_traits<decltype(&F::operator())>;
    public:
        using return_type = typename call_type::return_type;

        static constexpr std::size_t arity = call_type::arity - 1;

        template <std::size_t N>
        struct argument
        {
            static_assert(N < arity, "error: invalid parameter index.");
            using type = typename call_type::template argument<N+1>::type;
        };
};

template<class F>
struct function_traits<F&> : public function_traits<F>
{};

template<class F>
struct function_traits<F&&> : public function_traits<F>
{};

测试代码:

#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const int&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_same<typename function_traits<T>::template argument<0>::type, const A&>::value>::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }
};

void intFunc(const int&) {}
void aFunc(const A&){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}

【讨论】:

  • 这是不必要的限制,一般不会起作用;在 Boost 或标准库中采用多态 lambda 和多态可调用对象时,这些限制尤其成问题。
  • @R.MartinhoFernandes 这是正确的。此解决方案不解决多态 lambda。在 sfinae 的特殊情况下,它可以完成,因为您可以测试 lambda 是否支持使用某种类型的参数调用 beeing -> 请参阅我的其他答案
  • 我认为普遍的问题是我们不知道类型参数的确切用途是什么。如果它用于执行 sfinae,则可以测试仿函数是否支持被调用的蜜蜂。如果是出于其他目的,它不能与多态函子一起使用。但用例可能只支持非多态函子。
【解决方案3】:

如果 F 是 std::function,你应该能够使用它的 member type 并检查 `std::is_same':

template<class F>
void register_handler( F& f ) // any callable object
{
   // find out T - the argument type of f
   if(std::is_same<int, F::argument_type>::value)
   { .... }
   //etc .....

}

一个启动并运行的示例here

但这种代码很快就会变得难以维护。

【讨论】:

【解决方案4】:

您可以使用 sfinae 并测试您的参数是否可转换为带有所需参数的 std::function:

#include <type_traits>
#include <functional>
#include <iostream>

class A
{
};

template <class T>
struct Functor
{
  void operator()(const T& t)
  {}
};

struct Register
{
  //int parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (int)>, T>::value >::type* = 0)
  {
    std::cout << "Register int func" << std::endl;
  }

  //A parameters
  template <class T>
  static void RegisterFunctor(const T& /*functor*/, typename std::enable_if<std::is_constructible<typename std::function<void (A)>, T>::value >::type* = 0)
  {
    std::cout << "Register a func" << std::endl;
  }
};

void intFunc(int) {}
void aFunc(A){}

int main(int /*argc*/, char */*argv*/[])
{
  Functor<int> intFunctor;
  Functor<A> aFunctor;

  Register::RegisterFunctor(intFunctor);
  Register::RegisterFunctor(&intFunc);
  Register::RegisterFunctor(aFunctor);
  Register::RegisterFunctor(&aFunc);
  return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-30
    • 2020-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多