【问题标题】:Compile-Time Recursion via std::function & variadic templates通过 std::function 和可变参数模板进行编译时递归
【发布时间】:2014-06-30 13:36:55
【问题描述】:

我一直在探索 Visual Studio 2013 Pro 中 C++ 11 的许多新功能。到目前为止,我不得不说这很糟糕,但我在研究中遇到了一个障碍,我希望我能举起 mu 手并获得一些帮助。 (这个网站的忠实粉丝,第一次发布,如果代码设置不正确,请见谅)。

目前,我正在尝试创建一个将函数和/或成员函数 (operator ()) 绑定到 std::function. 的封装实例的通用方法

我正在使用可变参数模板参数来处理可变参数列表,通过可变参数的大小绘制参数的数量,并使用编译时递归中的值来抽取/返回正确数量的占位符进入绑定操作。

因为std::bind 需要一个实例而不是一个类型,所以编译时递归助手只是返回std::_Ph <N> 的实例。我使用帮助器的特殊化(std::_Ph<1>)作为分隔符,否则它只会变成一个无限循环,在编译期间淹没调用堆栈。

这是我正在使用的体验代码示例:

//Nothing special, just a polymorphic base
class iObject {

     public:

          virtual ~iObject () {};

};

/*Again nothing special, just a default null object implementation of an object that overloads operator ()*/
template < typename RETURN_TYPE, typename ... ARGS > 
class BaseCall : public iObject {

     public:

          ~BaseCall () {};

          virtual RETURN_TYPE operator () ( ARGS ... args ) {

               return 0;

          };

};

//One more bit of testing fodder, a concrete implementation of BaseCall
template < typename RETURN_TYPE, typename ... ARGS >
struct SampleMax : public BaseCall < RETURN_TYPE, ARGS ... > {

     private:

          /*deals with error C2903: 'result' : symbol is neither a class template not a function template*/
          typedef RETURN_TYPE result_type;

     //Inverted template method + trailing return
     protected:

          //Function assumes we are working with a sorted STL container
          template < typename FIRST_ARG >
          inline auto Implementation ( FIRST_ARG & Sample ) -> result_type {

               return * ( Sample.rbegin() );

          };

     public:

          /*For all public operator () members like this the code would be the same
          result_type operator () ( ARGS ... args ) {

               return Implementation ( args ... );

          };

};

//My compile-time helpers
//Got the idea from here: 
 //http://stackoverflow.com/questions/8759872/compile-time-recursion-and-conditionals

template < int N >
std :: _Ph < N > * CTRecursivePlaceholder () {

     return new std :: _Ph < N - 1 >;

};

//Specialization
template <>
std :: _Ph < 1 > * CTRecursivePlaceholder < 1 > () {

     return new std :: _Ph < 1 >;

};

//Here's the beef
template < typename RETURN_TYPE, typename ...ARGS >
class Experiment {

     public:  /*Ideally private, but for testing purposes it doesn't really matter right now*/

          std :: function < RETURN_TYPE ( ARGS ... ) Receiver;

    public:

          Experiment () {

               Receiver = std :: bind ( BaseCall < RETURN_TYPE, ARGS ... > (), * ( CTRecursivePlaceholder < ( sizeof ... ( ARGS ) ) > () ) );

          };

          virtual ~Experiment () {};

};

问题#1:在只需要一个参数的情况下,创建实例正常工作,以及绑定和调用独立函数:

/*pretend a vector of doubles called vec exists so I don't have to write it ;)*/

//...somewhere in a header...
double func ( std :: vector < double > & k )
{ return 1.0f; };

//...somewhere in a source file...
Experiment < double, std :: vector < double > & > exp;  //<-- instantiates just fine

exp.Receiver = & func;  //works
exp.Receiver ( vec );  //works

但是,当我尝试将 std::function 数据成员分配给 BaseCall 的子代时,例如我的 SampleMax 仿函数,如下所示:

SampleMax < double, std :: vector < double > & > max;

exp.Reciever = & max; //no go

我收到error C2064: term does not evaluate to a function taking 1 arguments。错误源自外部参照包头内部的 ,。

如果我在 Experiment 构造函数中直接使用占位符,它会起作用。

问题 #2:全面(独立函数和成员函数),如果我尝试创建一个带有两个或更多参数签名的 Experiment 实例,如下所示:

//...somewhere in a source file...
Experiment < double, std :: vector < double > &, std :: vector < double > & > exp;

我收到以下错误 (C2440):
'return' : cannot convert from 'std :: _Ph &lt; 1 &gt; *' to 'std :: _Ph &lt; 2 &gt; *'

如果我没记错的话,编译器总是优先考虑特化而不是模板定义,所以它可能是在特化而不是模板定义之后。

我也不太确定 std::_Ph 的模板定义中的 'N - 1' 语句,因为它应该在创建实例之前执行算术,但这就是示例运行的方式所以我也是这样做的。

请原谅任何错字,我在手机上写了整个内容,感谢您提供的任何帮助!

【问题讨论】:

  • std::_Ph?听起来不便携。 Here's a portable solution
  • 我不太明白为什么CTRecursivePlaceholder 使用new,返回一个指针,以及打算在哪里进行递归。
  • 据我所知,指向 &amp;max 之类的对象的指针是不可调用的。 max 本身是可调用的,但 exp.Receiver = max 将复制 max。 OTOH,exp.Receiver = std::ref(max) 将存储对 max 的引用。
  • _Ph 这个名字既没有出现在官方的 C++11 标准中,也没有出现在 n3797(相当新的 C++1y 草案)中。它很可能是您正在使用的标准库实现的实现细节。 “我必须返回该类型的一个实例,因此是新的”“如果我试图返回一个本地实例,它将超出范围。” Basic C++ 101: 值语义。您可以传递和返回按值new 不是必需的。
  • std::_Ph 是 Microsoft/Dinkumware 标准库实现的实现细节的一部分。尝试使用 g++/libstdc++ 或 clang++/libc++ 编译您的代码,您会注意到它们没有任何std::_Ph。这就是它不便携的原因。另一方面,std::placeholders::_1是标准的,这意味着您可以保证在标准库的任何实现中都有它们。

标签: generics c++11 recursion compile-time


【解决方案1】:

问题 #1 已经在dyp的评论中解决了

问题 #2 是您返回 std::_Ph* ,而 std::_Ph* 是预期的。

这里用 2 代替 N

template < int N >
std :: _Ph < N > * CTRecursivePlaceholder () {

     return new std :: _Ph < N - 1 >;

};

你会看到的。

附录 2014 年 7 月 2 日

这是一个用于递归构造绑定的 ansatz。

#include <functional> // bind, is_placeholder
/*[...]*/    

// placeholder
template<int N> struct PH {};

// It's ugly to extend std,
// but there's no other way to make arbitrary 
// placeholders known to std::bind
namespace std {
    template<int N>
    struct is_placeholder<PH<N>> 
        : public integral_constant<int, N> {};
}

// recursive sequential binder
// this simply pushes the placeholder types into the Args 
// result is in-order
template<class F, int N, class... Args>
struct SeqBinder : public SeqBinder<F, N-1, PH<N>, Args...> {};

// recursive sequential binder terminator specialization
template<class F, class... Args>
struct SeqBinder<F, 0, Args...> {
    static auto bind(F&& f)
        /*
         * HINT
         * here is the point to insert whatever invariant params
         * to the bind
         */
        -> decltype(std::bind(f, /* +whatever ,*/ Args()...)) {
             return std::bind(f, /* +whatever ,*/ Args()...); 
    } // bind()
}; // SeqBinder (terminator specialization)

您必须自定义/扩展它以使其适合您的需求;请注意,原则上将可调用的 N 元的 N 个参数依次绑定到 N 个占位符是毫无意义的,因为这是它们的自然绑定。

另一个附录 请记住,带符号的 int 模板参数是递归终止于 0 的潜在问题;您应该采取措施来捕获错误提供的负 N。

【讨论】:

  • @dyp 好的,对不起,dyp,没有得到你要去的地方,按价值返回似乎可以解决问题之一。现在,这两个助手都定义了一个本地实例按值返回。并且代码似乎适用于独立和类功能。仍然无法处理两个或更长的参数列表
  • 我知道我正在返回 std::_Ph* ,其中 std::_Ph* 是预期的,但我认为编译时递归能够处理它.是 语句吗?
  • 当然。 _Ph*N=*/2> 与 _Ph*N-1=*/1> 不同,也不是其祖先。
  • 我知道你想让我做什么,你想让我将返回类型模板 arg 从 N 更改为 N - 1,但这不会编译。我尝试了尾随返回类型、将调用嵌套在结构中以及其他一些重构,但没有骰子。至少在 Visual Studio 2013 Pro 中,没有不冗长的解决方案。我不能使用 constexp (预编译表达式),而 std :: integer_sequence 将是我将采取的另外两条路线,但在 11 月之前都不会在 VS 中可用。我想我得等到那时。还是谢谢。
  • 我并不是要“让 [你] 去做”任何事情。请注意我的答案的附录。
猜你喜欢
  • 1970-01-01
  • 2015-08-24
  • 2022-01-05
  • 2011-06-29
  • 2015-12-09
  • 1970-01-01
  • 2018-03-07
  • 1970-01-01
  • 2017-02-23
相关资源
最近更新 更多