9

这似乎是应该经常问和回答的问题,但我的搜索失败了。

我正在编写一个函数,我想接受某种通用的可调用对象(包括裸函数、手动仿函数对象bind、或std::function),然后在算法的深处调用它(即 lambda)。

该函数当前声明如下:

template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

我通过引用接受函子,因为我想保证它不会在进入函数时被复制,因此实际上调用了对象的相同实例。它是一个 const 引用,因为这是接受临时对象的唯一方法(这在使用手动仿函数或bind)。

但这需要函子实现operator()为 const。我不想这样要求;我希望它能够接受两者。

我知道我可以声明此方法的两个副本,一个接受它作为 const,另一个接受它作为非常量,以涵盖这两种情况。但我不想这样做,因为注释隐藏了很多我不想复制的代码(包括一些循环结构,所以我不能在不移动问题的情况下将它们提取到辅助方法) .

我也知道我可能会作弊const_cast在调用它之前将仿函数和非 const 作弊,但这感觉有潜在的危险(特别是如果仿函数有意实现 const 和非 const 调用运算符,则会调用错误的方法)。

我考虑过接受函子作为std::function/boost::function,但这感觉像是对应该是一个简单问题的沉重解决方案。(特别是在函子应该什么都不做的情况下。)

除了复制算法之外,是否有一种“正确”的方法来满足这些要求?

[注意:我更喜欢不需要 C++11 的解决方案,尽管我也对 C++11 的答案感兴趣,因为我在两种语言的项目中都使用了类似的结构。]

4

2 回答 2

3

您是否尝试过转发层来强制推断限定符?让编译器通过正常的模板实例化机制进行算法复制。

template<typename T, typename F>
size_t do_something_impl(const T& a, F& f)
{
   T internal_value(a);
   const T& c_iv = interval_value;
   // do some complicated things
   // loop {
   //   loop {
       f(c_iv, other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

template<typename T, typename F>
size_t do_something(const T& a, F& f)
{
   return do_something_impl<T,F>(a, f);
}

template<typename T, typename F>
size_t do_something(const T& a, const F& f)
{
   return do_something_impl<T,const F>(a, f);
}

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

包装器应该完全内联并且完全没有运行时成本,除了您最终可能会获得更多模板实例化(因此代码量更大),尽管这只会发生在没有operator()() constwhere const (或临时的)和非常量函子被通过。

于 2013-12-12T02:22:48.910 回答
1

回答新的放宽要求。

在对另一个答案的评论中,OP 已将要求澄清/更改为……

“我可以要求如果函子作为临时传入,那么它必须有一个 operator() const。我只是不想将其限制于此,这样如果函子没有作为临时传入(当然也不是 const 非临时),那么它允许有一个非常量 operator() ,这将被称为“

那么这根本不是问题:只需提供一个接受临时的重载。

有几种方法可以区分原始的基本实现,例如在 C++11 中一个额外的默认模板参数,在 C++03 中一个额外的默认普通函数参数。

但最清楚的是恕我直言,只是给它一个不同的名称,然后提供一个重载的包装器:

template<typename T, typename F>
size_t do_something_impl( T const& a, F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

template<typename T, typename F>
size_t do_something( T const& a, F const& f)
{ return do_something_impl( a, f ); }

template<typename T, typename F>
size_t do_something( T const& a, F& f)
{ return do_something_impl( a, f ); }

注意:不需要do_something_impl显式指定实例化,因为它是从左值参数推断出来的。

这种方法的主要特点是它支持更简单的调用,代价是当它有非时不支持临时作为参数const operator()

原答案:

您的主要目标是避免仿函数的复制,并接受一个临时参数作为实际参数。

在 C++11 中,您可以只使用右值引用,&&

对于 C++03,问题是一个临时仿函数实例作为实际参数,其中该仿函数具有非const operator().

一种解决方案是将负担转嫁给客户端代码程序员,例如

  • 要求实际参数是左值,而不是临时值,或者

  • 需要明确说明参数是临时的,然后将其作为引用const并使用const_cast.

例子:

template<typename T, typename F>
size_t do_something( T const& a, F& f)
{
   T internal_value(a);
   // do some complicated things
   // loop {
   //   loop {
       f(static_cast<const T&>(internal_value), other_stuff);
       // do some more things
   //   }
   // }
   return 42;
}

enum With_temp { with_temp };

template<typename T, typename F>
size_t do_something( T const& a, With_temp, F const& f )
{
    return do_something( a, const_cast<F&>( f ) );
}

如果希望直接支持临时const类型,以便在这种罕见的情况下也简化客户端代码程序员的生活,那么一个解决方案就是添加一个额外的重载:

enum With_const_temp { with_const_temp };

template<typename T, typename F>
size_t do_something( T const& a, With_const_temp, F const& f )
{
    return do_something( a, f );
}

感谢 Steve Jessop 和 Ben Voigt 对此案的讨论。


另一种更通用的 C++03 方法是提供以下两个小功能:

template< class Type >
Type const& ref( Type const& v ) { return v; }

template< class Type >
Type& non_const_ref( Type const& v ) { return const_cast<T&>( v ); }

然后do_something,正如上面在这个答案中给出的,可以被称为......

do_something( a, ref( MyFunctor() ) );
do_something( a, non_const_ref( MyFunctor() ) );

为什么我没有立即想到这一点,尽管我已经将这个解决方案用于字符串构建等其他事情:创建复杂性很容易,但更难简化!:)

于 2013-12-12T02:31:49.053 回答