【问题标题】:Pass a single parameter to a function that expects an iterator range将单个参数传递给需要迭代器范围的函数
【发布时间】:2018-10-14 20:58:31
【问题描述】:

考虑一个接受一个或多个参数(例如文件名)的函数。为了使其具有通用性,最好为通用迭代器范围编写它:

template<class Iter>
void function(Iter first, Iter last)
{
  // do something
}

现在我们可以通过以下方式调用它,与我们如何存储参数无关:

WhateverContainer container;
function(std::begin(container), std::end(container));

例如,STL 严重依赖这种范例。

现在,假设我们想要使用未存储在容器中的单个参数调用函数。当然可以这样写:

const int value = 5;
std::vector<int> vec(1, value);
function(std::begin(vec), std::end(vec));

但这个解决方案对我来说似乎很笨拙和浪费。

问题:是否有更好的低开销方法来创建单个变量的迭代器范围兼容表示?

【问题讨论】:

  • 其实我想我看错了你的例子,但无论如何现在它更清楚了
  • 出于好奇:您可以在单个元素上以有意义的方式使用适用于范围的哪些函数?
  • 例如可以处理一个或多个由其名称给出的文件的函数可能是一个用例。
  • @piripiri 不确定这是否是最好的设计,但可能最好让该功能仅在单个文件上运行并在范围上使用std::for_each。这可能并不总是合适,但它至少是一个总是值得考虑的替代方案......
  • @piripiri,如果函数调用的参数是独立的,请记住,一次只保留一个元素允许使用并行算法等操作,而无需额外工作。

标签: c++ stl iterator iterator-range


【解决方案1】:

您可以一次性使用指针:

function(&value, &value + 1);

在通用代码中,std::addressof 比一元运算符 &amp; 更安全,具体取决于您的偏执程度。

您当然可以将其包装在重载中以便于使用:

template <class T>
decltype(auto) function (T &&e) {
    auto p = std::addressof(e);
    return function(p, p + 1);
}

【讨论】:

【解决方案2】:

您还可以重载您的函数模板function 用于单元素范围

template<typename Iter>
void function(Iter first) {
    return function(first, std::next(first)); // calls your original function
}

这样,您的原始函数 function 仍然与迭代器范围兼容。但是请注意,将此重载与空范围一起使用将导致未定义的行为。


对于单个元素value,可以使用上面的重载:

function(&value); // calls overload

由于运算符&amp; 可能过载,请考虑使用std::addressof 而不是&amp;,正如this answer 中已经提到的那样。


对于由单个元素组成的范围,你也可以使用上面的重载,它只需要一个迭代器而不是一个迭代器对:

const int value = 5;
std::vector<int> vec(1, value); // single-element collection
function(std::begin(vec)); // <-- calls overload

【讨论】:

    【解决方案3】:

    我想我会分两步完成:

    1. 定义一个接受容器的模板函数的重载,按照迭代器版本编写。

    2. 定义一个代理类,它将对象引用视为大小为 1 的数组。

    c++17 示例:

    #include <iterator>
    #include <type_traits>
    #include <vector>
    #include <iostream>
    
    // proxy object
    template<class T>
    struct object_as_container
    {
        using value_type = T;
        using iterator = T*;
        using const_iterator = std::add_const_t<T>;
    
        object_as_container(value_type& val) : object_(val) {}
    
        const_iterator begin() const { return std::addressof(object_); }
        iterator begin() { return std::addressof(object_); }
    
        const_iterator end() const { return std::next(begin()); }
        iterator end() { return std::next(begin()); }
    
    private:
        value_type& object_;
    };
    
    // our function in terms of iterators    
    template<class Iter> void func(Iter first, Iter last)
    {
        while(first != last)
        {
            std::cout << *first++;
        }
    }
    
    // our function in terms of containers
    template<class Container> void func(Container&& cont)
    {
        func(cont.begin(), cont.end());
    }
    
    int main()
    {
        const int value = 5;
        func(object_as_container(value));
        func(std::vector { 1,2,3,4,5 });
    }
    

    【讨论】:

      【解决方案4】:

      您可以将其视为每个 [expr.unary.op]/3 一个元素的数组:

      function(&value, &value + 1);
      

      出于指针算术([expr.add])和比较([expr.rel],[expr.eq])的目的,不是以这种方式获取地址的数组元素的对象被认为属于到具有一个 T 类型元素的数组。

      【讨论】:

      • 在这种情况下,std::begin(value)std::end(value) 可以修改为返回&amp;value&amp;value + 1
      • @Lingxi,也许,但这不是很常见的情况,需要检测单个值与容器。一般来说,这可能很难。例如,假设您想传递一个容器。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-17
      • 2015-04-23
      • 2023-01-12
      相关资源
      最近更新 更多