Const和非常量函子

Const and non-const functors

本文关键字:常量 非常 Const      更新时间:2023-10-16

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

我正在编写一个函数,我想接受某种类型的通用可调用对象(包括裸函数、手动滚动函子对象、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;
}

我通过引用接受函子,因为我想保证它不会在进入函数时被复制,从而实际调用对象的同一实例。它是一个常量引用,因为这是接受临时对象的唯一方法(在使用手动滚动函子或bind时很常见)。

但这需要函子将operator()实现为const。我不想要求这样;我希望它能同时接受这两种情况。

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

我也知道,在调用函子之前,我可能会欺骗并const_cast函子为非常数,但这感觉有潜在的危险(尤其是如果函子有意同时实现const和非常数调用运算符,则会调用错误的方法)。

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

除了复制算法之外,还有什么"正确"的方法来满足这些要求吗?

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

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

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()() const的类型同时传递const(或临时)和nonconst函数时才会发生这种情况。

回答新的放宽要求

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

“如果函子作为临时函数传入,我可以要求则它必须具有运算符()常量。我只是不想限制它这样,如果函子不是作为临时(和当然也不是const非临时),则允许一个非常数运算符(),这将被称为”

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

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

但最清楚的是,IMHO只是给它一个不同的名称,然后提供一个过载的包装器:

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实例化,因为它是从左值参数推断出来的。

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

原答覆:

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

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

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

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

  • 要求实际参数为左值,而不是临时的、

  • 需要明确说明该参数是临时的,然后将其作为对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() ) );

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