Làm cách nào để tạo loại tùy chỉnh của tôi để hoạt động với các phạm vi dựa trên phạm vi của phạm vi phạm vi?


252

Giống như nhiều người ngày nay tôi đã thử các tính năng khác nhau mà C ++ 11 mang lại. Một trong những mục yêu thích của tôi là "phạm vi dựa trên phạm vi".

Tôi hiểu điều đó:

for(Type& v : a) { ... }

Tương đương với:

for(auto iv = begin(a); iv != end(a); ++iv)
{
  Type& v = *iv;
  ...
}

Và điều đó begin()chỉ đơn giản là trở lạia.begin() cho các container tiêu chuẩn.

Nhưng điều gì sẽ xảy ra nếu tôi muốn tạo loại tùy chỉnh "phạm vi dựa trên vòng lặp" của mình ?

Tôi có nên chỉ chuyên begin()end() ?

Nếu loại tùy chỉnh của tôi thuộc về không gian tên xml, tôi nên xác định xml::begin()hoặcstd::begin() ?

Tóm lại, các hướng dẫn để làm điều đó là gì?


Có thể bằng cách xác định một thành viên begin/endhoặc một người bạn, tĩnh hoặc miễn phí begin/end. Chỉ cần cẩn thận trong không gian tên mà bạn đặt chức năng miễn phí: stackoverflow.com/questions/28242073/iêu
alfC

Bất cứ ai cũng có thể vui lòng gửi câu trả lời với ví dụ về phạm vi giá trị nổi KHÔNG phải là vùng chứa : for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }. Tôi tò mò về cách bạn làm việc xung quanh thực tế là `operoperator! = ()` `Thật khó định nghĩa. Và những gì về dereferences ( *__begin) trong trường hợp này? Tôi nghĩ rằng nó sẽ là một đóng góp to lớn nếu ai đó cho chúng ta thấy cách đó được thực hiện!
BitTickler

Câu trả lời:


183

Tiêu chuẩn đã được thay đổi kể từ khi câu hỏi (và hầu hết các câu trả lời) được đăng trong độ phân giải của báo cáo lỗi này .

Cách để tạo một for(:)vòng lặp hoạt động trên loại của bạn Xbây giờ là một trong hai cách:

  • Tạo thành viên X::begin()X::end()trả về một cái gì đó hoạt động như một trình vòng lặp

  • Tạo một hàm miễn phí begin(X&)end(X&)trả về một cái gì đó hoạt động như một trình vòng lặp, trong cùng một không gian tên với kiểu của bạn X

Và tương tự cho constcác biến thể. Điều này sẽ làm việc cả trên các trình biên dịch thực hiện các thay đổi báo cáo lỗi và các trình biên dịch không.

Các đối tượng trả về không phải thực sự là các vòng lặp. Các for(:)vòng lặp, không giống như hầu hết các bộ phận của C ++ chuẩn, được quy định để mở rộng đến một cái gì đó tương đương với :

for( range_declaration : range_expression )

trở thành:

{
  auto && __range = range_expression ;
  for (auto __begin = begin_expr,
            __end = end_expr;
            __begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

trong đó các biến bắt đầu __chỉ để giải thích begin_exprend_exprlà phép thuật gọi begin/ end

Các yêu cầu về giá trị trả về bắt đầu / kết thúc rất đơn giản: Bạn phải quá tải trước ++, đảm bảo các biểu thức khởi tạo là hợp lệ, nhị phân !=có thể được sử dụng trong ngữ cảnh boolean, unary *trả về một cái gì đó bạn có thể gán - khởi tạo range_declarationvà hiển thị công khai phá hủy.

Làm như vậy theo cách không tương thích với iterator có lẽ là một ý tưởng tồi, vì các lần lặp lại trong tương lai của C ++ có thể tương đối khó khăn hơn về việc phá mã của bạn nếu bạn làm như vậy.

Bên cạnh đó, rất có khả năng một bản sửa đổi tương lai của tiêu chuẩn sẽ cho phép end_exprtrả lại một loại khác hơn begin_expr. Điều này hữu ích ở chỗ nó cho phép đánh giá "kết thúc lười biếng" (như phát hiện chấm dứt null) dễ dàng tối ưu hóa để có hiệu quả như vòng lặp C viết tay và các ưu điểm tương tự khác.


Lưu ý rằng for(:)các vòng lặp lưu trữ bất kỳ tạm thời nào trong một auto&&biến và chuyển nó cho bạn dưới dạng giá trị. Bạn không thể phát hiện nếu bạn đang lặp lại một giá trị tạm thời (hoặc giá trị khác); quá tải như vậy sẽ không được gọi bởi một for(:)vòng lặp. Xem [stmt. Sắp xếp] 1.2-1.3 từ n4527.

² Hoặc gọi begin/ endphương pháp, hoặc ADL chỉ tra cứu miễn phí chức năng begin/ end, hoặc ma thuật để hỗ trợ mảng C-phong cách. Lưu ý rằng std::beginkhông được gọi trừ khi range_expressiontrả về một đối tượng thuộc loại namespace stdhoặc phụ thuộc vào cùng.


Trong biểu thức cho phạm vi đã được cập nhật

{
  auto && __range = range_expression ;
  auto __begin = begin_expr;
  auto __end = end_expr;
  for (;__begin != __end; ++__begin) {
    range_declaration = *__begin;
    loop_statement
  }
}

với các loại __begin__endđã được tách rời.

Điều này cho phép iterator kết thúc không cùng loại với bắt đầu. Loại trình lặp cuối của bạn có thể là "sentinel" chỉ hỗ trợ !=với loại trình lặp bắt đầu.

Một ví dụ thực tế về lý do tại sao điều này hữu ích là trình lặp cuối của bạn có thể đọc "kiểm tra xem bạn char*có xem nó '0'" khi ==với a char*. Điều này cho phép một biểu thức phạm vi C ++ tạo mã tối ưu khi lặp qua char*bộ đệm kết thúc null .

struct null_sentinal_t {
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(Rhs const& ptr, null_sentinal_t) {
    return !*ptr;
  }
  template<class Rhs,
    std::enable_if_t<!std::is_same<Rhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(Rhs const& ptr, null_sentinal_t) {
    return !(ptr==null_sentinal_t{});
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator==(null_sentinal_t, Lhs const& ptr) {
    return !*ptr;
  }
  template<class Lhs,
    std::enable_if_t<!std::is_same<Lhs, null_sentinal_t>{},int> =0
  >
  friend bool operator!=(null_sentinal_t, Lhs const& ptr) {
    return !(null_sentinal_t{}==ptr);
  }
  friend bool operator==(null_sentinal_t, null_sentinal_t) {
    return true;
  }
  friend bool operator!=(null_sentinal_t, null_sentinal_t) {
    return false;
  }
};

ví dụ trực tiếp trong trình biên dịch không có hỗ trợ C ++ 17 đầy đủ; forvòng lặp mở rộng bằng tay.


Nếu dựa trên phạm vi để sử dụng một cơ chế tra cứu khác nhau, thì có thể bạn có thể sắp xếp dựa trên phạm vi đó để có một cặp chức năng beginendchức năng khác với mã có sẵn trong mã thông thường. Có lẽ sau đó họ có thể rất chuyên môn để hành xử khác đi (tức là nhanh hơn bằng cách bỏ qua đối số kết thúc để có được tối ưu hóa tối đa có thể.) Nhưng tôi không đủ tốt với các không gian tên để chắc chắn làm thế nào để làm điều này.
Aaron McDaid

@AaronMcDaid không thực tế lắm. Bạn dễ dàng kết thúc với kết quả đáng ngạc nhiên, bởi vì một số phương tiện gọi bắt đầu / kết thúc sẽ kết thúc với phạm vi dựa trên phạm vi cho bắt đầu / kết thúc, và những cách khác thì không. Những thay đổi vô hại (từ phía khách hàng) sẽ có những thay đổi về hành vi.
Yakk - Adam Nevraumont

1
Bạn không cần begin(X&&). Tạm thời bị đình chỉ trong không trung bởi auto&&trong một phạm vi dựa trên phạm vi và beginluôn được gọi với một lvalue ( __range).
TC

2
Câu trả lời này sẽ thực sự được hưởng lợi từ một ví dụ mẫu mà người ta có thể sao chép và thực hiện.
Tomáš Zato - Phục hồi Monica

Tôi muốn nhấn mạnh vào các thuộc tính của kiểu lặp (*, ++ ,! =). Tôi nên yêu cầu bạn viết lại câu trả lời này để làm cho thông số kỹ thuật lặp lặp mạnh hơn.
Màu đỏ. Có

62

Tôi viết câu trả lời của mình vì một số người có thể hạnh phúc hơn với ví dụ thực tế đơn giản mà không có STL bao gồm.

Tôi có triển khai mảng dữ liệu đơn thuần của riêng mình vì một số lý do và tôi muốn sử dụng phạm vi dựa trên vòng lặp. Đây là giải pháp của tôi:

 template <typename DataType>
 class PodArray {
 public:
   class iterator {
   public:
     iterator(DataType * ptr): ptr(ptr){}
     iterator operator++() { ++ptr; return *this; }
     bool operator!=(const iterator & other) const { return ptr != other.ptr; }
     const DataType& operator*() const { return *ptr; }
   private:
     DataType* ptr;
   };
 private:
   unsigned len;
   DataType *val;
 public:
   iterator begin() const { return iterator(val); }
   iterator end() const { return iterator(val + len); }

   // rest of the container definition not related to the question ...
 };

Sau đó là ví dụ sử dụng:

PodArray<char> array;
// fill up array in some way
for(auto& c : array)
  printf("char: %c\n", c);

2
Ví dụ này có các phương thức start () và end () và cũng có một lớp iterator ví dụ cơ bản (dễ hiểu) có thể dễ dàng điều chỉnh cho bất kỳ loại container tùy chỉnh nào. So sánh std :: mảng <> và bất kỳ triển khai thay thế nào có thể là một câu hỏi khác nhau và theo tôi không liên quan gì đến vòng lặp dựa trên phạm vi.
csjpeter

Đây là một câu trả lời rất súc tích và thiết thực! Đó chính xác là những gì tôi đang tìm kiếm! Cảm ơn!
Zac Taylor

1
Nó sẽ thích hợp hơn để loại bỏ vòng loại const trả lại cho const DataType& operator*(), và cho phép người dùng chọn sử dụng const auto&hay auto&? Dù sao cũng cảm ơn, câu trả lời tuyệt vời;)
Rick

53

Phần có liên quan của tiêu chuẩn là 6.5.4 / 1:

nếu _RangeT là một loại lớp, các id-un-uni bắt đầu và kết thúc được tra cứu trong phạm vi của lớp _RangeT như thể bằng cách tra cứu truy cập của thành viên lớp (3.4.5) và nếu một (hoặc cả hai) ít nhất một khai báo, hãy bắt đầu - expr và end-expr lần lượt là __range.begin()__range.end();

- nếu không, bắt đầu-expr và cuối expr là begin(__range)end(__range), tương ứng, nơi bắt đầu và kết thúc đều nhìn lên với tra cứu luận phụ thuộc vào (3.4.2). Đối với mục đích tra cứu tên này, không gian tên std là một không gian tên được liên kết.

Vì vậy, bạn có thể thực hiện bất kỳ thao tác nào sau đây:

  • xác định beginendchức năng thành viên
  • định nghĩa beginendcác hàm miễn phí sẽ được tìm thấy bởi ADL (phiên bản đơn giản hóa: đặt chúng trong cùng một không gian tên với lớp)
  • chuyên std::beginstd::end

std::beginbegin()dù sao cũng gọi hàm thành viên, vì vậy nếu bạn chỉ thực hiện một trong những điều trên, thì kết quả sẽ giống nhau cho dù bạn chọn cái nào. Đó là kết quả tương tự đối với các vòng lặp dựa trên phạm vi, và cũng là kết quả tương tự đối với mã xác thực không có quy tắc phân giải tên ma thuật của riêng nó, do đó, using std::begin;theo sau là một cuộc gọi không đủ tiêu chuẩn begin(a).

Tuy nhiên, nếu bạn triển khai các hàm thành viên các hàm ADL, thì các vòng lặp dựa trên phạm vi sẽ gọi các hàm thành viên, trong khi đó, các chỉ số sẽ gọi các hàm ADL. Tốt nhất hãy chắc chắn rằng họ làm điều tương tự trong trường hợp đó!

Nếu điều bạn đang viết thực hiện giao diện container, thì nó sẽ có begin()và các end()chức năng thành viên đã đủ, điều đó là đủ. Nếu đó là một phạm vi không phải là một container (đó sẽ là một ý tưởng tốt nếu nó không thay đổi hoặc nếu bạn không biết kích thước lên phía trước), bạn có thể tự do lựa chọn.

Trong số các tùy chọn bạn đặt ra, lưu ý rằng bạn không được quá tải std::begin(). Bạn được phép chuyên môn hóa các mẫu tiêu chuẩn cho loại do người dùng xác định, nhưng ngoài ra, việc thêm định nghĩa vào không gian tên std là hành vi không xác định. Nhưng dù sao, các hàm tiêu chuẩn chuyên dụng là một lựa chọn kém nếu chỉ vì thiếu chuyên môn hóa một phần nghĩa là bạn chỉ có thể làm điều đó cho một lớp duy nhất, không phải cho một mẫu lớp.


Không có yêu cầu nhất định mà trình vòng lặp đáp ứng nhiều? tức là một ForwardIterator hoặc một cái gì đó dọc theo những dòng đó.
Pubby

2
@Pubby: Nhìn vào 6.5.4, tôi nghĩ InputIterator là đủ. Nhưng thực sự tôi không nghĩ rằng các loại trở lại được một iterator ở tất cả cho phạm vi có trụ sở tại. Câu lệnh được định nghĩa trong tiêu chuẩn theo giá trị tương đương, do đó, chỉ đủ để thực hiện các biểu thức được sử dụng trong mã trong tiêu chuẩn: toán tử !=, tiền tố ++và đơn nguyên *. Có thể không khôn ngoan khi thực hiện begin()và các end()hàm thành viên hoặc các hàm ADL không phải là thành viên trả về bất cứ thứ gì ngoài iterator, nhưng tôi nghĩ đó là hợp pháp. Chuyên gia std::beginđể trả lại một người không lặp là UB, tôi nghĩ vậy.
Steve Jessop

Bạn có chắc chắn rằng bạn không được quá tải std :: bắt đầu? Tôi yêu cầu bởi vì thư viện tiêu chuẩn làm như vậy trong một vài trường hợp chính nó.
ThreeBit

@ThreeBit: vâng, tôi chắc chắn. Các quy tắc để thực hiện thư viện tiêu chuẩn khác với các quy tắc cho các chương trình.
Steve Jessop

3
Điều này cần được cập nhật cho open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1442 .
TC

34

Tôi có nên chỉ chuyên bắt đầu () và kết thúc ()?

Theo tôi biết, thế là đủ. Bạn cũng phải đảm bảo rằng việc tăng con trỏ sẽ nhận được từ đầu đến cuối.

Ví dụ tiếp theo (nó thiếu phiên bản const của bắt đầu và kết thúc) biên dịch và hoạt động tốt.

#include <iostream>
#include <algorithm>

int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }
    int * begin()
    {
        return &v[0];
    }
    int * end()
    {
        return &v[10];
    }

    int v[10];
};

int main()
{
    A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

Đây là một ví dụ khác với hàm bắt đầu / kết thúc. Chúng phải ở trong cùng một không gian tên với lớp, vì ADL:

#include <iostream>
#include <algorithm>


namespace foo{
int i=0;

struct A
{
    A()
    {
        std::generate(&v[0], &v[10], [&i](){  return ++i;} );
    }

    int v[10];
};

int *begin( A &v )
{
    return &v.v[0];
}
int *end( A &v )
{
    return &v.v[10];
}
} // namespace foo

int main()
{
    foo::A a;
    for( auto it : a )
    {
        std::cout << it << std::endl;
    }
}

1
@ereOn Trong cùng một không gian tên nơi lớp được định nghĩa. Xem ví dụ thứ 2
BЈовић

2
Xin chúc mừng :) Có lẽ nên đề cập đến các thuật ngữ Tra cứu phụ thuộc đối số (ADL) hoặc Tra cứu Koenig cho ví dụ thứ hai (để giải thích tại sao hàm miễn phí phải ở trong cùng một không gian tên như lớp mà nó hoạt động).
Matthieu M.

1
@ereOn: thực ra, bạn không. ADL là về việc mở rộng phạm vi để tra cứu để tự động bao gồm các không gian tên mà các đối số thuộc về. Có một bài viết tốt của ACCU về độ phân giải quá tải, không may bỏ qua phần tra cứu tên. Tra cứu tên liên quan đến việc thu thập chức năng của ứng viên, bạn bắt đầu bằng cách tìm trong phạm vi hiện tại + phạm vi của các đối số. Nếu không tìm thấy tên nào trùng khớp, bạn di chuyển đến phạm vi chính của phạm vi hiện tại và tìm kiếm lại ... cho đến khi bạn đạt đến phạm vi toàn cầu.
Matthieu M.

1
@ BЈовић xin lỗi, nhưng vì lý do nào trong hàm end () bạn trả về một con trỏ nguy hiểm? Tôi biết nó hoạt động, nhưng tôi muốn hiểu logic của việc này. Kết thúc của mảng là v [9], tại sao bạn lại trả về v [10]?
gedamial

1
@gedamial Tôi đồng ý. Tôi nghĩ rằng nó nên được return v + 10. &v[10]dereferences vị trí bộ nhớ vừa qua mảng.
Millie Smith

16

Trong trường hợp bạn muốn sao lưu trực tiếp một lớp lặp lại với lớp std::vectorhoặc std::mapthành viên của nó , đây là mã cho điều đó:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <vector>
using std::vector;
#include <map>
using std::map;


/////////////////////////////////////////////////////
/// classes
/////////////////////////////////////////////////////

class VectorValues {
private:
    vector<int> v = vector<int>(10);

public:
    vector<int>::iterator begin(){
        return v.begin();
    }
    vector<int>::iterator end(){
        return v.end();
    }
    vector<int>::const_iterator begin() const {
        return v.begin();
    }
    vector<int>::const_iterator end() const {
        return v.end();
    }
};

class MapValues {
private:
    map<string,int> v;

public:
    map<string,int>::iterator begin(){
        return v.begin();
    }
    map<string,int>::iterator end(){
        return v.end();
    }
    map<string,int>::const_iterator begin() const {
        return v.begin();
    }
    map<string,int>::const_iterator end() const {
        return v.end();
    }

    const int& operator[](string key) const {
        return v.at(key);
    }
    int& operator[](string key) {
        return v[key];
    } 
};


/////////////////////////////////////////////////////
/// main
/////////////////////////////////////////////////////

int main() {
    // VectorValues
    VectorValues items;
    int i = 0;
    for(int& item : items) {
        item = i;
        i++;
    }
    for(int& item : items)
        cout << item << " ";
    cout << endl << endl;

    // MapValues
    MapValues m;
    m["a"] = 1;
    m["b"] = 2;
    m["c"] = 3;
    for(auto pair: m)
        cout << pair.first << " " << pair.second << endl;
}

2
Nó đáng nói rằng const_iteratorcũng có thể được truy cập trong một auto(C ++ 11) cách -tương thích qua cbegin, cendvv
underscore_d

2

Ở đây, tôi đang chia sẻ ví dụ đơn giản nhất về việc tạo loại tùy chỉnh, sẽ hoạt động với " phạm vi dựa trên vòng lặp ":

#include<iostream>
using namespace std;

template<typename T, int sizeOfArray>
class MyCustomType
{
private:
    T *data;
    int indx;
public:
    MyCustomType(){
        data = new T[sizeOfArray];
        indx = -1;
    }
    ~MyCustomType(){
        delete []data;
    }
    void addData(T newVal){
        data[++indx] = newVal;
    }

    //write definition for begin() and end()
    //these two method will be used for "ranged based loop idiom"
    T* begin(){
        return &data[0];
    }
    T* end(){
        return  &data[sizeOfArray];
    }
};
int main()
{
    MyCustomType<double, 2> numberList;
    numberList.addData(20.25);
    numberList.addData(50.12);
    for(auto val: numberList){
        cout<<val<<endl;
    }
    return 0;
}

Hy vọng, nó sẽ hữu ích cho một số nhà phát triển người mới như tôi: p :)
Cảm ơn bạn.


tại sao không phân bổ thêm một yếu tố để tránh hội nghị bộ nhớ không hợp lệ trong phương thức kết thúc của bạn?
AndersK

@Anders Bởi vì hầu hết tất cả các trình kết thúc đều trỏ đến sau khi kết thúc cấu trúc chứa của chúng. Bản end()thân hàm rõ ràng không phải là vị trí bộ nhớ không phù hợp, vì nó chỉ lấy 'địa chỉ' của vị trí bộ nhớ này. Thêm một yếu tố bổ sung có nghĩa là bạn sẽ cần thêm bộ nhớ và sử dụng your_iterator::end()theo bất kỳ cách nào có thể coi là giá trị đó sẽ không hoạt động với bất kỳ trình lặp nào khác bởi vì chúng được xây dựng theo cùng một cách.
Qqwy

@Qqwy hủy bỏ phương thức kết thúc của mình - return &data[sizeofarray]IMHO chỉ cần trả về dữ liệu địa chỉ + sizeofarray nhưng tôi biết gì,
AndersK

@Anders Bạn đã đúng. Cảm ơn đã giữ cho tôi sắc nét :-). Vâng, data + sizeofarraysẽ là cách chính xác để viết này.
Qqwy

1

Câu trả lời của Chris Redford cũng hoạt động cho các container Qt (tất nhiên). Đây là một sự thích ứng (thông báo tôi trả về a constBegin(), tương ứng constEnd()từ các phương thức const_iterator):

class MyCustomClass{
    QList<MyCustomDatatype> data_;
public:    
    // ctors,dtor, methods here...

    QList<MyCustomDatatype>::iterator begin() { return data_.begin(); }
    QList<MyCustomDatatype>::iterator end() { return data_.end(); }
    QList<MyCustomDatatype>::const_iterator begin() const{ return data_.constBegin(); }
    QList<MyCustomDatatype>::const_iterator end() const{ return data_.constEnd(); }
};

0

Tôi muốn giải thích một số phần trong câu trả lời của @Steve Jessop, mà lúc đầu tôi không hiểu. Hy vọng nó giúp.

std::beginbegin()dù sao cũng gọi hàm thành viên, vì vậy nếu bạn chỉ thực hiện một trong những điều trên, thì kết quả sẽ giống nhau cho dù bạn chọn cái nào. Đó là kết quả tương tự đối với các vòng lặp dựa trên phạm vi, và cũng là kết quả tương tự đối với mã xác thực không có quy tắc phân giải tên ma thuật của riêng nó, do đó, using std::begin;theo sau là một cuộc gọi không đủ tiêu chuẩn begin(a).

Tuy nhiên, nếu bạn triển khai các hàm thành viên các hàm ADL , thì các vòng lặp dựa trên phạm vi sẽ gọi các hàm thành viên, trong khi đó, các chỉ số sẽ gọi các hàm ADL. Tốt nhất hãy chắc chắn rằng họ làm điều tương tự trong trường hợp đó!


https://en.cppreference.com/w/cpp/lingu/range-for :

  • Nếu ...
  • Nếu range_expressionlà một biểu thức của một loại lớp Ccó cả thành viên được đặt tên beginvà thành viên được đặt tên end(bất kể loại hoặc khả năng truy cập của thành viên đó), thì begin_expr__range.begin() và end_expr__range.end();
  • Mặt khác, begin_expris begin(__range)end_expris end(__range), được tìm thấy thông qua tra cứu phụ thuộc đối số (tra cứu không phải ADL không được thực hiện).

Đối với vòng lặp dựa trên phạm vi, các hàm thành viên được chọn trước tiên.

Nhưng cho

using std::begin;
begin(instance);

Các chức năng ADL được chọn đầu tiên.


Thí dụ:

#include <iostream>
#include <string>
using std::cout;
using std::endl;

namespace Foo{
    struct A{
        //member function version
        int* begin(){
            cout << "111";
            int* p = new int(3);  //leak I know, for simplicity
            return p;
        }
        int *end(){
            cout << "111";
            int* p = new int(4);
            return p;
        }
    };

    //ADL version

    int* begin(A a){
        cout << "222";
        int* p = new int(5);
        return p;
    }

    int* end(A a){
        cout << "222";
        int* p = new int(6);
        return p;
    }

}

int main(int argc, char *args[]){
//    Uncomment only one of two code sections below for each trial

//    Foo::A a;
//    using std::begin;
//    begin(a);  //ADL version are selected. If comment out ADL version, then member functions are called.


//      Foo::A a;
//      for(auto s: a){  //member functions are selected. If comment out member functions, then ADL are called.
//      }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.