【问题标题】:How to determine the type of a function parameter given the type of argument passed to it?给定传递给它的参数类型,如何确定函数参数的类型?
【发布时间】:2025-12-31 11:50:12
【问题描述】:

我需要一个类型特征,它会根据函子的类型和传递给它的参数的类型报告函子的operator() 参数的类型。基本上,我需要准确地确定在将参数传递给函子时将其转换为什么类型。为简单起见,假设我只对带有单个参数的(可能是模板化的,可能重载的)operator() 感兴趣。不幸的是,我仅限于 c++03。可以做到吗?如果没有,c++11怎么样?

这是一个例子:

#include <cassert>
#include <type_traits>

template<typename Functor, typename Argument>
  struct parameter_type
{
  // what goes here?
  typedef ... type;
};

struct takes_float_cref
{
  void operator()(const float &);
};

int main()
{
  // when calling takes_float_cref::operator() with an int,
  // i'd expect a conversion to const float &
  assert(std::is_same(parameter_type<takes_float_cref, int>::type, const float &>::value);

  return 0;
}

related question(他的回答并不能完全满足我的需要)给出了需要这种特征的背景。我已经对ideone 进行了进一步的单元测试。

【问题讨论】:

  • 你不能简单地在每个仿函数中添加相同的typedef 吗? typedef float const &amp; TParameter; 之类的东西在 takes_float_cref 中,然后只测试 Functor::TParameter
  • @AxxA,谢谢你的建议。是的,这是一种解决方案,但我发现要求客户这样做是很严厉的。我不确定它是否能很好地扩展到 N 参数的一般情况。
  • 嗯,实际上可能没那么糟糕。在这里打字太长了,我会发布它作为答案。看看对你有没有帮助。
  • @AzzA:主要缺点是接受多种类型参数(模板或重载)的函子无法轻松定义这样的typedef
  • +1 表示有单元测试的问题!

标签: c++ templates c++11 parameter-passing


【解决方案1】:

恐怕如果没有您的客户的帮助,这完全不可能。

TL;DR:单元测试fail (grrr gcc)。

您问题的一般情况是这个函子:

struct Functor {
  template <typename T>
  typename std::enable_if<std::is_integral<T>::value>::type
  operator()(T t) const;

  void operator(double d) const;
};

这里结合了两个主要问题:

  1. 如果存在重载,则将 &amp;F::operator() 设为给定类型需要 static_cast 以消除应使用哪个重载的歧义
  2. 模板(以及表达它们的任意条件)不能简洁地表达为typedefs

因此,如果您真的希望获得这种类型,客户端(此处为Functor)需要为您提供额外的钩子。如果没有decltype,我不知道如何获得它(注意,gcc 提供了typeof 作为 C++03 中的扩展)。

让客户给我们提示:

// 1. Make use of the return value:
struct Functor {
  template <typename T>
  typename std::enable_if<std::is_integral<T>::value, T>::type
  operator()(T t) const;

  double operator(double d) const;
};

// 2. Double up the work (but leave the return value as is)
struct Functor {
  template <typename T>
  static typename std::enable_if<std::is_integral<T>::value, T>::type Select(T);

  static double Select(T);

  template <typename T>
  typename std::enable_if<std::is_integral<T>::value>::type
  operator()(T t) const;

  void operator(double d) const;
};

假设我们选择第二种情况(将返回值留作其他用途)。

template <typename F, typename T>
struct parameter {
  static T t;
  typedef decltype(F::Select(t)) type;
};

在 C++03 中,用 gcc 替换 decltypetypeof

我没有办法放弃decltypesizeof 确实提供了未评估的上下文,但在这里似乎没有多大帮助。

Unit Tests Here.

不幸的是,引用似乎存在一个 gcc 错误,float&amp; 减少为float(以及任何其他引用),错误仍然存​​在于decltype 所以它只是一个错误的实现:/ Clang 3.0 对 C++11 版本(decltype)没有问题但我认为没有实现typeof

这可以通过要求客户端使用ref&lt;float&gt; 类来解决,然后解包它。只是多了一点负担……

【讨论】:

  • 请注意,其中一个单元测试显然是错误的:takes_float_cref 的测试写得好像它需要 bar const&amp;。那怎么会过去呢?
  • @Xeo:确切地说,使用assert 意味着我们永远不会走那么远。我会修补测试。
  • @Xeo: test patched, Ive go for compile-time failure 它表明gcc 只是忽略了*引用:x
  • @MatthieuM。谢谢!我不确定我是否理解客户在您的第一个示例中给我的提示。 (1.利用返回值)你能解释一下吗?
  • @JaredHoberock:如果您可以强制客户端使其operator() 返回与其参数相同的参数,那么您可以直接使用它。这完全限制了可实现的目标(并且没有通过您的测试用例),因此更容易让他复制其工作并提供“选择”重载。唯一的问题是他可能更难让operator()Select 保持同步......这两种情况都远非完美:x
【解决方案2】:

要开始,我会这样做:

template<typename F>
struct parameter_type_impl;

// may be with variadic arguments
template<typename R, typename A, typename F>
struct parameter_type_impl<R (F::*)(A)> {
  typedef A type;
};

template<typename F>
struct parameter_type {
  typedef typename parameter_type_impl<decltype(&F::operator())>::type type;
};

我不明白你为什么要传入实际的参数类型。如果 无法进行转换,您必须使用特殊措施 (例如 SFINAE)稍后。我认为这两件事是正交的: 推断参数类型,然后决定是否要使用参数 喜欢传入是可以兑换的。

非 C++03 的 decltype 很难摆脱。指定函数 type 总是需要参数的知识。只要你愿意 把论据拼出来,整个事情就没有实际意义了。

Boost.Function Types 也会出现同样的问题。

【讨论】:

  • 谢谢。我希望我需要传入实际的参数类型来区分将调用哪个重载,以及哪个可能模板化的operator() 将被实例化。
  • 哦,您的问题与重载无关。如果是这样,你已经为失败启航了。重载的函数必须始终通过 static_cast 来消除歧义,这总是需要您拼出类型。所以你还需要传入返回类型。如果没有 C++11,这会很快变得丑陋。
  • 我的错。我在ideone上发布的代码清楚地说明了我感兴趣的所有案例。丑陋的我可以处理,我只是想知道它是否可能:)
  • 据我所知。给定一个重载函数和一个参数,我不知道如何获取将通过模板元编程调用的函数的类型。
  • @JaredHoberock 看看我在这里的尝试:无论如何,这并不能解决你的问题。 *.com/questions/8935520/…
【解决方案3】:
    #include <iostream>

    template< typename PParameter00 = void, typename PParameter01 = void, typename PParameter02 = void, typename PParameter03 = void >
    struct TIdentityParameter // Users need to inherit from it. Add more types as needed.
    {
      typedef PParameter00 TType00;
      typedef PParameter01 TType01;
      typedef PParameter02 TType02;
      typedef PParameter03 TType03;
    };

    struct TUserFunctor00 : public TIdentityParameter< float const &, int, void * >
    {
      void operator()( float const &, int, void * );
      // or they can do
      //void operator()( TType00, TType01, TType02 );
    };

    struct TUserFunctor01 : public TIdentityParameter< char const *, double >
    {
      void operator()( char const*, double );
      // or they can do
      //void operator()( TType00, TType01 );
    };

    template< bool pValue >
    struct TValueBool
    {
      static bool const sValue = pValue;
    };

    template< typename PType00, typename PType01 >
    struct TIsSame : public TValueBool< false >
    {
    };

    template< typename PType >
    struct TIsSame< PType, PType > : public TValueBool< true >
    {
    };

    int main( void )
    {
     std::cout << TIsSame< TUserFunctor00::TType02, void * >::sValue << std::endl;
     std::cout << TIsSame< TUserFunctor01::TType00, double >::sValue << std::endl;

     return ( 0 );
    }

Code on [ideone][1]. I don't think it's asking too much from users to inherit from your struct in a pattern explained to them. After all, they want to work with your library. Anyway, maybe it's not what you are looking for.

++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++

编辑:这可能有点接近 JAred 正在寻找的功能,但是,我知道,这种风格对他没有吸引力。虽然,在 C++03 中,我看不出你可以如何以不同的方式做到这一点。请注意,您可以让TIdentityParameter 使用 16 个模板参数来涵盖 16 种可能的类型。再一次,是的,用户必须继承并指定类型。 Ideone:

#include <iostream>

struct TOneCrazyStruct
{
};

template< typename PParameter00 = TOneCrazyStruct, typename PParameter01 = TOneCrazyStruct, typename PParameter02 = TOneCrazyStruct,
  typename PParameter03 = TOneCrazyStruct, typename PParameter04 = TOneCrazyStruct >
struct TIdentityParameter //Users will need to inherit from this struct as shown below.
{
  typedef PParameter00 TType00;
  typedef PParameter01 TType01;
  typedef PParameter02 TType02;
  typedef PParameter03 TType03;
  typedef PParameter04 TType04;
};

struct TUserFunctor00 : public TIdentityParameter< float const &, int, void *, double >
{
  void operator()( float const &, int, void * );
  void operator()( double );
};

template< bool pValue >
struct TValueBool
{
  static bool const sValue = pValue;
};

template< typename PType00, typename PType01 >
struct TIsSame : public TValueBool< false >
{
};

template< typename PType >
struct TIsSame< PType, PType > : public TValueBool< true >
{
};

template< typename PFunctor, typename PParameter >
struct THasType : public TValueBool<
  TIsSame< typename PFunctor::TType00, PParameter >::sValue || TIsSame< typename PFunctor::TType01, PParameter >::sValue
    || TIsSame< typename PFunctor::TType02, PParameter >::sValue || TIsSame< typename PFunctor::TType03, PParameter >::sValue >
{
};

int main( void )
{
 std::cout << THasType< TUserFunctor00, void * >::sValue << std::endl;
 std::cout << THasType< TUserFunctor00, long double >::sValue << std::endl;

 return ( 0 );
 }

【讨论】:

  • 那么,为什么不从std::binary_functionstd::unary_function 继承并完成它呢?这也不包括重载。
  • @AzzA 感谢您的建议,但正如 pmr 所指出的,此练习的全部原因是避免此类事情。此外,c++11 弃用了std::unary_functionstd::binary_function
  • @JaredHoberock 哦,好吧,抱歉没用。
  • @AzzA C++03 中没有n-arguments。 (好吧,预处理器是极限。)。对于重载:您的基类将如何处理重载 operator() 的类?
  • @pmr 好的,我已经发布了稍微修改过的版本。我明白,这不是 Jared 想要的,只是为了解决您的顾虑。