使用declval

时间:2018-06-18 08:53:41

标签: c++ iterator c++14 decltype

一个最小的例子,它显示了两种获取迭代器类型的方法,我会天真地期望它给出与结果相同的类型:

template <typename Range>
struct foo
{
    using iterator = decltype(std::begin(std::declval<Range>()));
    using iterator2 = typename Range::iterator;

    static_assert(std::is_same<iterator, iterator2>::value, "Iterator types differ!");
};

int main()
{
    std::vector<int> v;
    foo<decltype(v)> f;
}

但这实际上触发了静态断言。

如果我们将第一个迭代器更改为:

using iterator = decltype(std::declval<Range>().begin());

没有触发静态断言。

查看std::begin()的定义,对于引用和const引用类型都会重载。由于declval给出了一个rvalue-reference,因此它只会绑定到一个const引用,从而返回一个const迭代器类型。

这可以使用参考折叠来解决一些令人厌恶的事情:

using iterator = decltype(std::begin(std::declval<typename std::add_lvalue_reference<Range>::type>()));

是否有更简单的方法来获取非const迭代器?显然typename Range::iterator并不适用于所有类型(例如T*),同样适用于成员begin(),因此两者都不理想。

1 个答案:

答案 0 :(得分:3)

直接调用std::begin甚至不是获取开始迭代器的正确方法。正确的习语需要两个陈述:using std::begin; begin(rng);。显然你无法在decltype中做到。

您执行此操作的方式不适用于没有成员begin / endstd::begin/end超载的类型。< / p>

所以正确的解决方案是创建一个执行此操作的函数:

template<typename Range>
auto my_begin(Range &&rng)
{
  using std::begin;
  return begin(std::forward<Range>(rng));
}

然后在decltype字段中调用该函数。