【问题标题】:Specialize C++ template function that uses generic iterators for pointer value type?专门针对指针值类型使用通用迭代器的 C++ 模板函数?
【发布时间】:2020-01-05 20:24:53
【问题描述】:

我有一个接受泛型迭代器作为参数的 C++ 模板函数,如下所示:

(它处理从first 到但不包括last 的所有元素)

void update(const std::uint8_t *const data, const size_t len)
{
    /* ... */
}

template<typename iterator_type>
void update(const iterator_type &first, const iterator_type &last)
{
    typedef typename std::iterator_traits<iterator_type>::value_type value_type;
    for (iterator_type iter = first; iter != last; ++iter)
    {
        update(reinterpret_cast<const std::uint8_t*>(std::addressof(*iter)), sizeof(value_type));
    }
}

到目前为止,这是有效的。我唯一的问题是:如果迭代器的 value_type 恰好是指针类型,那么这个函数处理的是指针值(内存地址)而不是指针所指向的实际值(对象) .所以,我想专门处理指针类型。在 C++ 中有没有一种好方法可以为 value_type 是指针类型的迭代器提供此模板函数的特化?

我尝试使用单个函数和 if(is_pointer)... else... 构造来解决这个问题,如下所示:

void update(const iterator_type &first, const iterator_type &last)
{
    typedef typename std::iterator_traits<iterator_type>::value_type value_type;
    if(std::is_pointer<value_type>::value)
    {
        for (iterator_type iter = first; iter != last; ++iter)
        {
            update(reinterpret_cast<const std::uint8_t*>(*iter), sizeof(std::pointer_traits<value_type>::element_type));
        }
    }
    else
    {
        for (iterator_type iter = first; iter != last; ++iter)
        {
            update(reinterpret_cast<const std::uint8_t*>(std::addressof(*iter)), sizeof(value_type));
        }
    }
}

...但是,不幸的是,这不适用于非指针类型:

error C2039: 'element_type': is not a member of 'std::pointer_traits<value_type>'

【问题讨论】:

  • 你的编译器支持 C++17 吗?如果是这样,只需将您的 if 更改为 if constexpr。有关详细信息,请参阅针对 C++17 更新的 C++ 书籍。
  • 为了兼容性,代码应该用VS2010及更高版本(以及MinGW、GCC和Clang)编译,所以我更喜欢使用C++17 - 仅功能。
  • 如果有人打算使用该函数作用于指针怎么办?我认为与 std 库如何工作相一致的更清晰的解决方案是让调用者使用一些执行取消引用的迭代器适配器或让update 采用可选的转换 lambda。
  • 在我的上下文中,如果调用者有一个指针容器(std::vector、std::list 等),那么处理指针(更多或更少的“随机”内存地址)作为数据;但是假设调用者实际上想要处理这些指针指向的数据是保存的。

标签: c++ visual-studio-2010 iterator function-templates


【解决方案1】:

编写模板函子来更新单个项目并对其进行专门化更容易。然后,让您的更新模板函数调用该类。比如:

template <typename T>
struct Update {
    void operator () (const T &v) {
        //...
    }
};

template <typename T>
struct Update<T *> {
    void operator () (const T *v) {
        //...
    }
};


template <typename T>
void update (const T &v) {
    Update<T>()(v);
}

然后在循环内部,你可以这样称呼它:

update(*iter);

您可以将更新函子参数化到更新函数模板中,以便为调用者提供与默认更新处理不同的操作的灵活性。

template <typename T, typename U = Update<T>>
void update (const T &v, U u = U{}) {
    u(v);
}

然后,之前的调用仍然有效,但您也可以传入不同的可调用对象(如 lambda)。

update(*iter, [](int *){ /* ... */ });

【讨论】:

  • 这个基本的想法对我很有效。然而,specializing 模板化的 update() 函数不起作用,所以我只创建了两个单独的重载 - 一个用于指针,一个用于 refs。
  • @MuldeR:抱歉,已修复。
【解决方案2】:

这个问题已经有了很好的答案,但我会在这里提出类型特征和标签分派使指针和非指针值类型的迭代器之间的重载变得容易:

template <typename T>
void update_tag_dispatch(T first, T last, std::true_type) {
    std::cout << "iterator value is a pointer!\n";
}

template <typename T>
void update_tag_dispatch(T first, T last, std::false_type) {
    std::cout << "iterator value is not a pointer\n";
}

template <typename T>
void update_tag_dispatch(T first, T last) {
    update_tag_dispatch(first, last, std::is_pointer<std::remove_reference_t<decltype(*first)>>{});
}

你也可以使用它的表弟std::enable_if_t

template <typename T, std::enable_if_t<std::is_pointer<std::remove_reference_t<decltype(*std::declval<T>())>>::value>* = nullptr>
void update_enable_t(T first, T last) {
    std::cout << "iterator value is a pointer!\n";
}
template <typename T, std::enable_if_t<!std::is_pointer<std::remove_reference_t<decltype(*std::declval<T>())>>::value>* = nullptr>
void update_enable_t(T first, T last) {
    std::cout << "iterator value is a pointer!\n";
}

演示:https://godbolt.org/z/t7YbcF

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-21
    • 2015-08-12
    • 1970-01-01
    • 1970-01-01
    • 2013-10-28
    • 1970-01-01
    • 2011-02-03
    相关资源
    最近更新 更多