【问题标题】:Beginning generically, plus decltype considering local using-declaration一般开始,加上 decltype 考虑本地 using-declaration
【发布时间】:2011-08-28 01:16:23
【问题描述】:

C++0x 的 ranged-for 循环对处理数组有一个特殊的例外(FDIS §6.5.4),并且有两个函数 std::begin 和 end 被重载以处理数组或选择 begin/结束方法。这让我相信可以编写一个接受泛型序列的函数来匹配 ranged-for 循环的行为:

template<class C>
void f(C &c) {
  using std::begin;
  using std::end;
  do_something_with(begin(c), end(c));
}

如果 C 的命名空间中有一个“更具体”的 begin/end,它将通过 ADL 选择,否则代码“默认”为 std::begin/end。

但是,ranged-for 有这个特殊例外是有原因的。如果在命名空间中传递一个类型的数组,其带有一个指针的语义不同的 begin/end,则不会选择 std::begin/end 的数组形式:

namespace ns {
  struct A {};
  void begin(A*);  // Does something completely different from std::begin.
}

void f_A() {  // Imagine above f() called with an array of ns::A objects.
  ns::A c[42];
  using std::begin;
  begin(c);  // Selects ns::begin, not array form of std::begin!
}

为避免这种情况,是否有比编写自己的开始/结束包装器(在内部使用 ADL)并显式调用它们而不是 std::begin 或 ADLized 开始更好的解决方案?

namespace my {
  template<class T>
  auto begin(T &c)  // Also overload on T const &c, as std::begin does.
  -> decltype(...)  // See below.
  {
    using std::begin;
    return begin(c);
  }

  template<class T, int N>
  T* begin(T (&c)[N]) {
    return c;
  }
}
// my::end omitted, but it is analogous to my::begin.

template<class C>
void f(C &c) {
  do_something_with(my::begin(c), my::end(c));
}

但是,如上面的省略号所示,我什至不知道如何编写 my::begin!对于该 decltype,我如何选择将通过本地 using-declaration 和 ADL 选择的类型?

【问题讨论】:

  • 我花了大约 30 分钟来思考和写下这个问题,但这是我脑海中酝酿了一段时间的事情,我认为只是写它给了我一个解决“ decltype 考虑本地使用声明”,但它并不漂亮。如果今天没有人提及,我会自行回答。
  • 我觉得还有一个问题:难道 my::begin 不会被考虑为“return begin(c);”,如果选择的话,是无限递归吗?
  • @Fred:第二个问题很简单,只需要在单独的“detail”命名空间中添加一个额外的函数包装器。
  • 返回typename T::iterator怎么样?
  • 就我个人而言,我会寻找一个特质——比如typename range_traits&lt;T&gt;::iterator。这将需要定义range_traits 类,但是处理一般情况的range_traits 类不会太难编写..(可惜我们在c++0x 中没有概念了)

标签: c++ templates c++11 argument-dependent-lookup


【解决方案1】:

我在使用元组时也遇到过同样的情况:

template<typename Tuple>
auto f(Tuple&& tuple)
-> /* ??? */
{
    using std::get;
    return get<Idx>(tuple);
}

接受std::tupleboost::tuple,同时接受左值和右值,而不是template&lt;typename... Types&gt; auto f(std::tuple&lt;Types...&gt;&amp; tuple) -&gt; /* ??? */

这个特殊情况是通过一个特性类解决的,它实际上是由标准提供的:std::tuple_element。像往常一样,特征类的想法是 tuple 是一个协议,任何想要符合它的东西都将提供一个专门化,例如tuple_element。所以就我而言,解决方案已经存在。

就您而言,如果您正在编写一个库,我建议您编写(并记录)这样一个特征类。在应用程序代码或其他情况下,我不太确定。

【讨论】:

  • Traits 听起来是个好主意,但我完全不确定如何以一种不需要将每种类型的专门化传递给 my:: 的方式将其应用于这种情况开始。
【解决方案2】:

您可以自己对数组进行特殊处理。数组的类型是(并且必须是开始/结束工作)ElementType (&amp;)[Size],所以如果你重载函数,比如:

template<class C, size_t S>
void f(C (&c)[S]) {
  do_something_with(std::begin(c), std::end(c));
}

它的行为应该特别像 for 循环。

顺便说一句,你不需要std::beginstd::end 那么它们是微不足道的:

template<class C, size_t S>
void f(C (&c)[S]) {
  do_something_with(c, c + S);
}

(可能需要强制转换;我实际上只将它用于需要指针的东西,而不是任何迭代器)。

另一方面,beginend 函数采用指针是相当愚蠢的事情。如果指向的对象是一个集合,那么它们可能应该被引用。

【讨论】:

  • 我计划自己对数组参数进行特殊处理,但使用 my::begin 所以我只需要执行一次,而不是重载函数 f、g、h、i、j...所有这些函数,我都可以调用 my::begin。
  • "使用指针的开始和结束函数是相当愚蠢的事情" 傻吗?也许吧,但是如果有人碰巧在一个对我来说未知的命名空间中传递了一个类型,我怎么能写一个不会随机中断(它仍然可能编译和运行!)开始还是结束?
猜你喜欢
  • 2012-05-17
  • 2021-07-21
  • 1970-01-01
  • 1970-01-01
  • 2022-11-25
  • 2019-04-10
  • 2018-11-08
  • 2015-10-16
  • 2017-01-25
相关资源
最近更新 更多