Const和非const仿函数

时间:2013-12-12 02:00:24

标签: c++ const functor function-object

这似乎应该经常被问及回答,但我的搜索功能让我失望。

我正在编写一个函数,我想接受某种类型的通用可调用对象(包括裸函数,手动仿函数对象,bindstd::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,以便涵盖这两种情况。但是我不想这样做,因为评论隐藏了很多我不想复制的代码(包括一些循环结构,因此我无法将它们提取到辅助方法中)不只是移动问题)。

我也知道在调用它之前我可能会欺骗和const_cast非const的仿函数,但这感觉有潜在的危险(特别是如果函子有意地实现const和non,则会调用错误的方法) -const call operators)。

我已经考虑过将这个仿函数作为std::function / boost::function来接受,但这对于应该是一个简单的问题来说是一个重要的解决方案。 (特别是在仿函数不做任何事情的情况下。)

是否有&#34;权利&#34;如何在不重复算法的情况下满足这些要求?

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

2 个答案:

答案 0 :(得分: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

包装器应该是完全内联的,并且根本没有运行时成本,除了你可能最终会有更多的模板实例化(因此代码量更大)这一事实,尽管这只会在没有{{ 1}}其中const(或临时)和非const仿函数都被传递。

答案 1 :(得分:1)

回答新的宽松要求。

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

  

“如果函数作为临时函数传入,我可以要求它   那么它必须有一个operator()const。我只是不想限制它   这样,如果一个仿函数没有作为临时传递(和   当然也不是const非临时的)然后它被允许   一个非const运算符(),这将被称为“

然后根本不是问题:只提供一个接受临时的重载。

有几种方法可以区分原始的基本实现,例如:在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() ) );

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