【问题标题】:Match iterable types (arrays and classes with begin()/end())匹配可迭代类型(带有 begin()/end() 的数组和类)
【发布时间】:2014-10-03 03:26:58
【问题描述】:

我编写了类型特征,例如可以用来测试给定类型是否“可迭代”的类。这适用于数组(对于T[N],不适用于T[])以及具有beginend 方法的类,它们返回看起来像迭代器的东西。我想知道它是否可以比我做得更简洁/简单?

尤其是 impl 命名空间中的东西看起来有点迂回/hacky。在我看来,这一切都有些难看。有关使用它并且可以使用 g++ 和 clang++ 编译的示例,请参见:https://gist.github.com/panzi/869728c9879dcd4fffa8

template<typename T>
struct is_iterator {
private:
    template<typename I> static constexpr auto test(void*)
        -> decltype(
            *std::declval<const I>(),
            std::declval<const I>() == std::declval<const I>(),
            std::declval<const I>() != std::declval<const I>(),
            ++ (*std::declval<I*>()),
            (*std::declval<I*>()) ++,
            std::true_type()) { return std::true_type(); }

    template<typename I> static constexpr std::false_type test(...) { return std::false_type(); }

public:
    static constexpr const bool value = std::is_same<decltype(test<T>(0)), std::true_type>::value;
};

namespace impl {
    // implementation details

    template<typename T>
    struct has_iterable_methods {
    private:

        template<typename C> static constexpr auto test(void*)
            -> decltype(
                std::declval<C>().begin(),
                std::declval<C>().end(),
                std::true_type()) { return std::true_type(); }

        template<typename C> static constexpr std::false_type test(...) { return std::false_type(); }

    public:
        static constexpr const bool value = std::is_same<decltype(test<T>(0)), std::true_type>::value;
    };

    template<typename T, bool HasIterableMethods>
    struct returns_iterators : public std::false_type {};

    template<typename T>
    struct returns_iterators<T, true> {
        typedef decltype(std::declval<T>().begin()) begin_type;
        typedef decltype(std::declval<T>().end())   end_type;

        static constexpr const bool value =
            std::is_same<begin_type, end_type>::value &&
            is_iterator<begin_type>::value;
    };
}

template<typename T>
struct is_iterable : public std::integral_constant<
    bool,
    impl::returns_iterators<
        typename std::remove_const<T>::type,
        impl::has_iterable_methods<typename std::remove_const<T>::type>::value>::value> {};

template<typename T, std::size_t N>
struct is_iterable<T[N]> : public std::true_type {};

template<typename T>
struct is_iterable<T*> : public std::false_type {};

【问题讨论】:

  • pretty printer 具有相似的类型特征。
  • ++ (*std::declval&lt;I*&gt;()) 如果你想创建一个左值,为什么不直接++ std::declval&lt;I&amp;&gt;()
  • begin(std::declval&lt;T&gt;()) 可能比std::declval&lt;T&gt;().begin() 更好。
  • @Jarod42 在我看来,整个事情都可以通过 enable-ADL-technique using std::begin; .. begin(std::declval&lt;T&amp;&gt;()) 来简化。
  • 这个问题属于 Stack Exchange 网络中的另一个站点:codereview.stackexchange.com

标签: c++ templates typetraits c++14


【解决方案1】:

首先,一些样板可以在 std::begin 可见的上下文中对 begin 进行简单的参数相关查找:

#include <utility>
#include <iterator>
namespace adl_details {
  using std::begin; using std::end;
  template<class R>
  decltype(begin(std::declval<R>())) adl_begin(R&&r){
    return begin(std::forward<R>(r));
  }
  template<class R>
  decltype(end(std::declval<R>())) adl_end(R&&r){
    return end(std::forward<R>(r));
  }
}
using adl_details::adl_begin;
using adl_details::adl_end;

这是合理模拟基于范围的for(:) 循环如何找到它们的开始/结束迭代器所必需的。通过这样打包,我们减少了下面的样板。

接下来,一些 C++1y 风格的实用程序别名:

template<class>struct sink {using type=void;};
template<class X>using sink_t=typename sink<X>::type;
template<bool b, class T=void>using enable_if_t=typename std::enable_if<b,T>::type;

sink_t 接受任何类型,并用void 替换它。

enable_if_t 删除下面烦人的typename 垃圾邮件。

在工业强度库中,我们将其放入 details,并有一个 1-type-argument 版本发送给它。但我不在乎:

template<class I,class=void> struct is_iterator:std::false_type{};
template<> struct is_iterator<void*,void>:std::false_type{};
template<> struct is_iterator<void const*,void>:std::false_type{};
template<> struct is_iterator<void volatile*,void>:std::false_type{};
template<> struct is_iterator<void const volatile*,void>:std::false_type{};
template<class I>struct is_iterator<I,
  sink_t< typename std::iterator_traits<I>::value_type >
>:std::true_type{};

is_iterator 不会对Iiterator_traits 进行大量审核。但这已经足够了。

template<class R>
using begin_t=decltype(adl_begin(std::declval<R&>()));
template<class R>
using end_t=decltype(adl_end(std::declval<R&>()));

这两个类型别名使下面的东西不那么烦人了。

再次,在工业强度库中,将 2-arg-with-void 放入 details

template<class R,class=void> struct has_iterator:std::false_type{};
template<class R>
struct has_iterator<
  R,
  enable_if_t<
    is_iterator<begin_t<R>>::value
    && is_iterator<end_t<R>>::value
    // && std::is_same<begin_t<R>,end_t<R>>::value
  >
>:std::true_type{};

请注意上面enable_if_t 中的注释行。我忽略了它以允许非对称迭代工作,其中end 是具有不同operator== 重载的类型。 C++17 正在考虑这样做:它允许对以空字符结尾的字符串进行非常非常有效的算法(例如)。

最后,最终的输出:

template<class R>using iterator_t=enable_if_t<has_iterator<R>::type, begin_t<R>>;

如果它有一个,则计算为可迭代范围R 的迭代器。

在某些情况下这不起作用,但它们是病态的。

live example

【讨论】:

  • 但是你检查的要求比 OP 少。目前,您只检查 begin(t) 是否有效,而 OP 检查更多(病态)案例(因为 begin() 不返回迭代器类型......)
  • 有趣的解决方案。但是为什么adl_begin/adl_end函数不直接使用std::begin/std::end呢? “adl”代表什么?
  • @panzi adl 是“参数依赖查找”。上述技术意味着如果在与类型参数相同的命名空间中存在beginend,它将优先于std::begin 找到,如果没有,则使用std::begin。这是for(:) 循环如何找到begin/end 迭代器的相当不错的近似值。简单地调用 std::begin 不会成功。
  • @panzi 缺少两个 &amp; 添加,has_iterator&lt;int[3]&gt;::value 现在为 true。 (int[3] 类型的右值没有有效的begin,呵呵)。请注意,我故意将is_same 排除在外:不仅返回具有不同不兼容迭代器的beginend 是一种病态的情况,如果它们兼容可以导致有用的优化。让我看看能不能找到这篇文章……
  • @panzi here -- 使用非迭代器 end 的可迭代对象而不是范围可能有用(并且正在下线)
猜你喜欢
  • 1970-01-01
  • 2016-11-26
  • 2014-01-24
  • 2020-07-12
  • 2020-09-19
  • 2019-04-20
  • 1970-01-01
  • 2020-10-14
相关资源
最近更新 更多