簡體   English   中英

在基於范圍的 for 循環中將原始指針視為范圍

[英]Viewing a raw pointer as a range in range-based for-loop

對於 for-range 循環語法,如何使原始指針表現得像一個范圍。

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;// will not execute if the pointer is null

動機:

現在人們普遍認為boost::optional (future std::optional ) 值可以被視為一個范圍,因此可以在 for 范圍循環中使用http://faithandbrave.hateblo.jp/entry/2015/01/29 /173613

當我重寫我自己的簡化版本時:

namespace boost {
    template <class Optional>
    decltype(auto) begin(Optional& opt) noexcept{
        return opt?&*opt:nullptr;
    }

    template <class Optional>
    decltype(auto) end(Optional& opt) noexcept{
        return opt?std::next(&*opt):nullptr;
    }
}

用作

boost::optional<int> opt = 3;
for (int& x : opt) std::cout << x << std::endl;

在查看該代碼時,我想象它也可以推廣到原始(可為空)指針。

double five = 5;
double* dptr = &five;
for(int& d : dptr) std::cout << d << std::endl;

而不是通常的if(dptr) std::cout << *dptr << std::endl; . 這很好,但我想實現上面的其他語法。

嘗試

首先,我嘗試使上述Optional版本的beginend對指針起作用,但我不能。 所以我決定在類型中明確並刪除所有模板:

namespace std{ // excuse me, this for experimenting only, the namespace can be removed but the effect is the same.
    double* begin(double* opt){
        return opt?&*opt:nullptr;
    }
    double* end(double* opt){
        return opt?std::next(&*opt):nullptr;
    }
}

差不多了,它適用於

for(double* ptr = std::begin(dptr); ptr != std::end(dptr); ++ptr) 
    std::cout << *ptr << std::endl;

但它不適用於所謂的等效for-range 循環:

for(double& d : dptr) std::cout << d << std::endl;

兩個編譯器告訴我: error: invalid range expression of type 'double *'; no viable 'begin' function available error: invalid range expression of type 'double *'; no viable 'begin' function available

到底是怎么回事? 是否有編譯器魔法禁止范圍循環為指針工作。 我是否對遠程循環語法做出了錯誤的假設?

具有諷刺意味的是,在標准中有std::begin(T(&arr)[N])的重載,這非常接近。


注意和第二次雖然

是的,這個想法很愚蠢,因為即使可能,這也會非常令人困惑:

double* ptr = new double[10];
for(double& d : ptr){...}

只會迭代第一個元素。 一個更清晰也更現實的解決方法是做一些類似於@Yakk 提出的解決方法:

for(double& d : boost::make_optional_ref(ptr)){...}

通過這種方式,很明顯我們只迭代一個元素並且該元素是可選的。

好的,好的,我會回到if(ptr) ... use *ptr

因為基於范圍的工作方式是(來自 §6.5.4):

begin-exprend-expr確定如下
— 如果_RangeT是數組類型,[..]
— 如果_RangeT是一個類類型,[..]
— 否則, begin-exprend-expr分別是begin(__range)end(__range) ,其中beginend在關聯的命名空間 (3.4.2) 中查找。 [ 注意:不執行普通的不合格查找 (3.4.1)。 ——尾注]

在這種情況下,關聯的命名空間是什么? (第 3.4.2/2 節,強調我的):

命名空間和類的集合按以下方式確定:
(2.1) — 如果T是基本類型,則其關聯的命名空間和類集都是空的

因此,沒有地方放置您的double* begin(double*)以便它會被基於范圍的for語句調用。

您想要做的解決方法是制作一個簡單的包裝器:

template <typename T> 
struct PtrWrapper {
    T* p;
    T* begin() const { return p; }
    T* end() const { return p ? p+1 : nullptr; }
};

for (double& d : PtrWrapper<double>{dptr}) { .. }

認為for(:)循環是通過“在 ADL 激活的上下文中調用std::beginstd::end ”來實現的,這是一個有用的謊言。 但那是謊言。

相反,該標准基本上對std::beginstd::end本身進行了並行實現。 這可以防止語言的低級構造依賴於它自己的庫,這似乎是一個好主意。

該語言對begin的唯一查找是基於 ADL 的查找。 不會找到您的指針的std::begin ,除非您是指向std某些內容的指針。 編譯器不會以這種方式找到std::begin( T(&)[N} ) ,而是該語言對迭代進行了硬編碼。

namespace boost {
  template<class T>
  T* begin( optional<T>&o ) {
    return o?std::addressof(*o):nullptr;
  }
  template<class T>
  T* begin( optional<T&>&&o ) {
    return o?std::addressof(*o):nullptr;
  }
  template<class T>
  T const* begin( optional<T> const&o ) {
    return o?std::addressof(*o):nullptr;
  }
  template<class T>
  T* end( optional<T>&o ) {
    return o?std::next(begin(o)):nullptr;
  }
  template<class T>
  T* end( optional<T&>&&o ) {
    return o?std::next(begin(o)):nullptr;
  }
  template<class T>
  T const* end( optional<T> const&o ) {
    return o?std::next(begin(o)):nullptr;
  }
  template<class T>
  boost::optional<T&> as_optional( T* t ) {
    if (t) return *t;
    return {};
  }
}

現在你可以:

void foo(double * d) {
  for(double& x : boost::as_optional(d)) {
    std::cout << x << "\n";
}

無需重復double類型。

請注意,非引用optional的右值返回T const* ,而T& optonal的右值返回T* 在寫作上下文中迭代臨時文件可能是一個錯誤。

TL; 博士

此構造可用於范圍 for 循環:

std::views::counted(raw_ptr, !!raw_ptr)

細節

C++20 提供了大量使用范圍庫來創建臨時可迭代對象的方法。 例如

#include <ranges>
#include <iostream>
 
int main()
{
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    int *raw_ptr = a;
    
    for(int i : std::views::counted(raw_ptr, 10))
        std::cout << i << ' ';
    std::cout << '\n';
    
    for(int i : std::views::counted(raw_ptr, 1))
        std::cout << i << ' ';
    std::cout << '\n';
    
    std::cout << "empty for null pointer pointer\n";
    raw_ptr = nullptr;
    for(int i : std::views::counted(raw_ptr, 0))
        std::cout << i << ' ';
    std::cout << '\n';
    
    std::cout << "Exit\n";
}

印刷

1 2 3 4 5 6 7 8 9 10 
1

empty for null pointer

Exit

類似地, std::views::subrange可以與 (start, end] 指針一起使用。檢查以獲取更多信息。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM