【问题标题】:Const and non-const functorsconst 和非 const 函子
【发布时间】:2013-12-12 02:00:24
【问题描述】:

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

我正在编写一个函数,我想接受某种通用的可调用对象(包括裸函数、手动仿函数对象、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,另一个接受它作为非常量,以涵盖这两种情况。但我不想这样做,因为 cmets 隐藏了很多我不想复制的代码(包括一些循环结构,所以我不能在不移动问题的情况下将它们提取到辅助方法) .

我也知道我可能会在调用它之前将仿函数 const_cast 欺骗为非常量,但这感觉有潜在的危险(特别是如果仿函数有意实现 const 和非常量,则会调用错误的方法呼叫操作员)。

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

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

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

【问题讨论】:

  • 嗯,按值获取可调用对象是很常见的。无论如何,为什么不将主代码放在另一个函数中,让 const/non-const 版本调用它并传递 f(...) 的结果?
  • C++11 中,您将使用“通用参考”(F&amp;&amp;),请参阅 Scott Meyers 关于该主题的重要信息。
  • 我有一条经验法则:当隐式转换有效时,切勿使用强制转换。 (关于你的internal_value
  • @chris:如果我按价值接收它,它将被复制。这是不可接受的。
  • @BenVoigt:强制转换旨在“鼓励”仿函数以正确的方式接受参数,或者生成编译错误。特别是我想强制我想要的参数类型,即使仿函数也是模板化的。

标签: c++ constants functor function-object


【解决方案1】:

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

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

包装器应该是完全内联的并且完全没有运行时开销,除了你可能会得到更多的模板实例化(因此代码量更大),尽管这只会发生在没有 @987654323 的类型时@ 常量(或临时)和非常量仿函数都通过的地方。

【讨论】:

  • 这对于operator() 不是const 的临时函子参数如何工作?
  • 我认为这是一个非解决方案
  • @Alf:为什么?该问题明确要求提供一种方法,既让 const 引用调用 operator()() const,又让非 const 引用调用 operator()() /* not const */。并明确表示在 const 引用上调用后者是错误的。
  • @Ben:我不认为提问者对允许右值参数使用 const 引用结束这一事实感到高兴。我认为他们希望能够在非常量右值参数上调用非常量 operator(),并且正在努力解决 C++03 不允许他们这样做的事实。因此,尽管提问者说在 const 引用上调用非 const operator() 是错误的,但我不认为他们的意思是暗示在非 const 右值参数上调用 operator() 是错误的,只要他们能弄清楚实现这一目标的一种方法。右值引用在 C++11 中的作用是哪个 ofc。
  • @Steve:提问者肯定有这种感觉,但他不是这样。他所说的是两个重载将具有所需的行为,但会遭受代码重复的缺点。垫片层解决了什么问题。
【解决方案2】:

回答新的宽松要求。

在对另一个答案的评论中,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 中,您可以只使用右值引用,&amp;&amp;

对于 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() ) );

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

【讨论】:

  • 这将调用错误的operator()() 重载,并使用const_cast 没有任何好处。请参阅我的答案以了解正确的方法(至少 g++ 接受它,并且我认为它完全符合标准)。
  • @BenVoigt:我看不出它如何“调用错误的 `operator()() 重载”。请举个例子。
  • @Ben:这就是“需要明确说明参数是临时的”的意思。如果包括Temporary 在内的重载文档说,“您必须提供一个临时的:例如不是对const 对象的引用”,那么调用非常量operator() 就可以了。像do_something(a, with_temp, some_reference_to_const) 这样称呼它的任何人都没有RTFM,他们有错。
  • 来自问题:“(特别是如果仿函数有意同时实现 const 和非 const 调用运算符,则会调用错误的方法)。”
  • @BenVoigt:问题中的引述是关于以const 调用临时对象时出现错误的过载。在上面,调用是临时的非const,正是为了避免这个问题。如果您仍然认为它是错误的,请举一个具体的例子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-12
相关资源
最近更新 更多