为什么在c++ 11中使用非成员的开始和结束函数?

时间:2023-01-15 17:47:40

Every standard container has a begin and end method for returning iterators for that container. However, C++11 has apparently introduced free functions called std::begin and std::end which call the begin and end member functions. So, instead of writing

每个标准容器都有一个开始和结束方法来返回该容器的迭代器。然而,c++ 11显然引入了称为std::begin和std::end的免费函数,它们调用begin和end成员函数。因此,而不是写作

auto i = v.begin();
auto e = v.end();

you'd write

你会写

using std::begin;
using std::end;
auto i = begin(v);
auto e = end(v);

In his talk, Writing Modern C++, Herb Sutter says that you should always use the free functions now when you want the begin or end iterator for a container. However, he does not go into detail as to why you would want to. Looking at the code, it saves you all of one character. So, as far as the standard containers go, the free functions seem to be completely useless. Herb Sutter indicated that there were benefits for non-standard containers, but again, he didn't go into detail.

在编写现代c++的文章中,Herb Sutter说,当您想要一个容器的开始迭代器或结束迭代器时,您应该始终使用这些*函数。然而,他没有详细说明你为什么想这样做。看看代码,它可以为您节省所有的一个字符。因此,就标准容器而言,*函数似乎是完全无用的。Herb Sutter指出非标准容器有好处,但他没有详细说明。

So, the question is what exactly do the free function versions of std::begin and std::end do beyond calling their corresponding member function versions, and why would you want to use them?

那么,问题是std::begin和std::end的免费函数版本到底是什么,除了调用它们对应的成员函数版本之外,还有什么原因需要使用它们呢?

6 个解决方案

#1


146  

How do you call .begin() and .end() on a C-array ?

如何在c数组中调用.begin()和.end() ?

Free-functions allow for more generic programming because they can be added afterwards, on a data-structure you cannot alter.

*函数允许进行更一般的编程,因为它们可以在之后添加到无法更改的数据结构上。

#2


34  

Consider the case when you have library that contain class:

当您拥有包含类的库时,请考虑以下情况:

class SpecialArray;

it has 2 methods:

它有两个方法:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

to iterate over it's values you need to inherit from this class and define begin() and end() methods for cases when

要遍历它的值,需要从这个类继承,并为情况定义begin()和end()方法

auto i = v.begin();
auto e = v.end();

But if you always use

但是如果你经常用的话

auto i = begin(v);
auto e = end(v);

you can do this:

你可以这样做:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

where SpecialArrayIterator is something like:

SpecialArrayIterator是这样的:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

now i and e can be legally used for iteration and accessing of values of SpecialArray

现在i和e可以合法地用于SpecialArray的值的迭代和访问

#3


30  

Using the begin and end free functions adds one layer of indirection. Usually that is done to allow more flexibility.

使用开始和结束*函数增加了一个间接层。通常这样做是为了增加灵活性。

In this case I can think of a few uses.

在这种情况下,我能想到一些用途。

The most obvious use is for C-arrays (not c pointers).

最明显的用法是c -array(而不是c指针)。

Another is when trying to use a standard algorithm on a non-conforming container (ie the container is missing a .begin() method). Assuming you can't just fix the container, the next best option is to overload the begin function. Herb is suggesting you always use the begin function to promote uniformity and consistency in your code. Instead of having to remember which containers support method begin and which need function begin.

另一种方法是尝试在不符合要求的容器上使用标准算法(即容器缺少.begin()方法)。假设您不能仅修复容器,那么下一个最佳选项是重载begin函数。Herb建议您总是使用begin函数来促进代码的一致性和一致性。不需要记住哪些容器支持方法开始,哪些需要函数开始。

As an aside, the next C++ rev should copy D's pseudo-member notation. If a.foo(b,c,d) is not defined it instead tries foo(a,b,c,d). It's just a little syntactic sugar to help us poor humans who prefer subject then verb ordering.

顺便说一句,下一个c++ rev应该复制D的伪成员表示法。如果a.foo(b,c,d)没有定义,而是尝试foo(a,b,c,d)。这只是一点点语法糖,帮助我们这些可怜的人,他们更喜欢主语而不是动词的顺序。

#4


15  

To answer your question, the free functions begin() and end() by default do nothing more than call the container's member .begin() and .end() functions. From <iterator>, included automatically when you use any of the standard containers like <vector>, <list>, etc., you get:

要回答您的问题,空闲函数begin()和end()默认情况下仅仅调用容器的成员.begin()和.end()函数。从 ,使用任何标准容器如 等时自动包含,得到:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

The second part of you question is why prefer the free functions if all they do is call the member functions anyway. That really depends on what kind of object v is in your example code. If the type of v is a standard container type, like vector<T> v; then it doesn't matter if you use the free or member functions, they do the same thing. If your object v is more generic, like in the following code:

第二个问题是如果*函数只调用成员函数,为什么要选择它呢?这取决于示例代码中对象v的类型。如果v的类型是标准容器类型,如向量 v;不管你用的是*函数还是成员函数,它们都是一样的。如果你的对象v更通用,如下面的代码所示:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

Then using the member functions breaks your code for T = C arrays, C strings, enums, etc. By using the non-member functions, you advertise a more generic interface that people can easily extend. By using the free function interface:

然后使用成员函数破坏T = C数组、C字符串、枚举等的代码。使用免费功能界面:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

The code now works with T = C arrays and C strings. Now writing a small amount of adapter code:

代码现在与T = C数组和C字符串一起工作。现在编写少量适配器代码:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

We can get your code to be compatible with iterable enums too. I think Herb's main point is that using the free functions is just as easy as using the member functions, and it gives your code backward compatibility with C sequence types and forward compatibility with non-stl sequence types (and future-stl types!), with low cost to other developers.

我们还可以让您的代码与可迭代枚举兼容。我认为Herb的主要观点是,使用*函数和使用成员函数一样简单,并且它使您的代码与C序列类型向后兼容,并与非stl序列类型(和future-stl类型!)向前兼容,对其他开发人员来说成本更低。

#5


4  

Whereas the non-member functions don't provide any benefit for the standard containers, using them enforces a more consistent and flexible style. If you at some time want to extend an existing non-std container class, you'd rather define overloads of the free functions, instead of altering the existing class's definition. So for non-std containers they are very useful and always using the free functions makes your code more flexible in that you can substitute the std container by a non-std container more easily and the underlying container type is more transparent to your code as it supports a much wider variety of container implementations.

而非成员函数并不能为标准容器提供任何好处,使用它们可以实现更一致和更灵活的风格。如果您有时想要扩展一个现有的非std容器类,您宁愿定义空闲函数的重载,而不是修改现有类的定义。所以对于non-std容器他们非常有用,并且随时使用免费功能使您的代码更加灵活,你可以用std non-std容器的容器更容易和底层容器类型更透明代码,因为它支持更广泛的各种各样的容器实现。

But of course this always has to be weighted properly and over abstraction is not good either. Although using the free functions is not that much of an over-abstraction, it nevertheless breaks compatibility with C++03 code, which at this young age of C++11 might still be an issue for you.

但是,当然,这总是需要适当地权衡,而过度抽象也不好。虽然使用免费函数并不是太过抽象,但是它破坏了与c++ 03代码的兼容性,在c++ 11这个年轻的时代,这对您来说仍然是一个问题。

#6


2  

One benefit of std::begin and std::end is that they serve as extension points for implementing standard interface for external classes.

std的一个好处是:开始和std::end是为外部类实现标准接口的扩展点。

If you'd like to use CustomContainer class with range-based for loop or template function which expects .begin() and .end() methods, you'd obviously have to implement those methods.

如果您想要使用基于区间的CustomContainer类,则希望使用.begin()和.end()方法,您显然必须实现这些方法。

If the class does provide those methods, that's not a problem. When it doesn't, you'd have to modify it*.

如果类确实提供了这些方法,那么这不是问题。如果没有,你必须修改它*。

This is not always feasible, for example when using external library, esspecially commercial and closed source one.

这并不总是可行的,例如在使用外部库时,特别是商业和封闭源代码库时。

In such situations, std::begin and std::end come in handy, since one can provide iterator API without modifying the class itself, but rather overloading free functions.

在这种情况下,std::begin和std::end就派上用场了,因为可以在不修改类本身的情况下提供迭代器API,而是重载*函数。

Example: suppose that you'd like to implement count_if function that takes a container instead of a pair of iterators. Such code might look like this:

示例:假设您希望实现count_if函数,它使用一个容器而不是一对迭代器。这样的代码可能是这样的:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

Now, for any class you'd like to use with this custom count_if, you only have to add two free functions, instead of modifying those classes.

现在,对于您想要使用这个定制count_if的任何类,您只需添加两个空闲函数,而不必修改这些类。

Now, C++ has a mechanisim called Argument Dependent Lookup (ADL), which makes such approach even more flexible.

现在,c++有了一个叫做参数依赖查找(ADL)的机制,这使得这种方法更加灵活。

In short, ADL means, that when a compiler resolves an unqualified function (i. e. function without namespace, like begin instead of std::begin), it will also consider functions declared in namespaces of its arguments. For example:

简而言之,ADL意味着,当编译器解析一个非限定函数(即没有名称空间的函数,比如begin而不是std::begin)时,它还将考虑在其参数的名称空间中声明的函数。例如:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

In this case, it doesn't matter that qualified names are some_lib::begin and some_lib::end - since CustomContainer is in some_lib:: too, compiler will use those overloads in count_if.

在这种情况下,限定名是否为some_lib: begin和some_lib: end——因为CustomContainer在some_lib:: too中,编译器将在count_if中使用这些重载。

That's also the reason for having using std::begin; and using std::end; in count_if. This allows us to use unqualified begin and end, therefore allowing for ADL and allowing compiler to pick std::begin and std::end when no other alternatives are found.

这也是使用std: begin的原因;和使用std::结束;在count_if。这允许我们使用非限定的开始和结束,因此允许ADL,并允许编译器选择std::begin和std::end,当没有找到其他替代时。

We can eat the cookie and have the cookie - i. e. have a way to provide custom implementation of begin/end while the compiler can fall back to standard ones.

我们可以吃这个cookie并拥有这个cookie——也就是说,我们可以提供开始/结束的自定义实现,而编译器可以返回到标准实现。

Some notes:

一些注意事项:

  • For the same reason, there are other similar functions: std::rbegin/rend, std::size and std::data.

    出于同样的原因,还有其他类似的功能:std::rbegin/rend, std::size和std::data。

  • As other answers mentions, std:: versions have overloads for naked arrays. That's useful, but is simply a special case of what I've described above.

    正如其他答案提到的,std::版本对裸数组有重载。这很有用,但只是我上面描述的一个特例。

  • Using std::begin and friends is particularly good idea when writing template code, because this makes those templates more generic. For non-template you might just as well use methods, when applicable.

    在编写模板代码时,使用std::begin和friends是一个非常好的主意,因为这使得这些模板更加通用。对于非模板,您可以在适当的时候使用方法。

P. S. I'm aware that this post is nearly 7 years old. I came across it because I wanted to answer a question which was marked as a duplicate and discovered that no answer here mentions ADL.

p。s。我知道这个帖子已经快7年了。我偶然发现了这个问题,因为我想回答一个被标记为重复的问题,并且发现这里没有答案提到ADL。

#1


146  

How do you call .begin() and .end() on a C-array ?

如何在c数组中调用.begin()和.end() ?

Free-functions allow for more generic programming because they can be added afterwards, on a data-structure you cannot alter.

*函数允许进行更一般的编程,因为它们可以在之后添加到无法更改的数据结构上。

#2


34  

Consider the case when you have library that contain class:

当您拥有包含类的库时,请考虑以下情况:

class SpecialArray;

it has 2 methods:

它有两个方法:

int SpecialArray::arraySize();
int SpecialArray::valueAt(int);

to iterate over it's values you need to inherit from this class and define begin() and end() methods for cases when

要遍历它的值,需要从这个类继承,并为情况定义begin()和end()方法

auto i = v.begin();
auto e = v.end();

But if you always use

但是如果你经常用的话

auto i = begin(v);
auto e = end(v);

you can do this:

你可以这样做:

template <>
SpecialArrayIterator begin(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, 0);
}

template <>
SpecialArrayIterator end(SpecialArray & arr)
{
  return SpecialArrayIterator(&arr, arr.arraySize());
}

where SpecialArrayIterator is something like:

SpecialArrayIterator是这样的:

class SpecialArrayIterator
{
   SpecialArrayIterator(SpecialArray * p, int i)
    :index(i), parray(p)
   {
   }
   SpecialArrayIterator operator ++();
   SpecialArrayIterator operator --();
   SpecialArrayIterator operator ++(int);
   SpecialArrayIterator operator --(int);
   int operator *()
   {
     return parray->valueAt(index);
   }
   bool operator ==(SpecialArray &);
   // etc
private:
   SpecialArray *parray;
   int index;
   // etc
};

now i and e can be legally used for iteration and accessing of values of SpecialArray

现在i和e可以合法地用于SpecialArray的值的迭代和访问

#3


30  

Using the begin and end free functions adds one layer of indirection. Usually that is done to allow more flexibility.

使用开始和结束*函数增加了一个间接层。通常这样做是为了增加灵活性。

In this case I can think of a few uses.

在这种情况下,我能想到一些用途。

The most obvious use is for C-arrays (not c pointers).

最明显的用法是c -array(而不是c指针)。

Another is when trying to use a standard algorithm on a non-conforming container (ie the container is missing a .begin() method). Assuming you can't just fix the container, the next best option is to overload the begin function. Herb is suggesting you always use the begin function to promote uniformity and consistency in your code. Instead of having to remember which containers support method begin and which need function begin.

另一种方法是尝试在不符合要求的容器上使用标准算法(即容器缺少.begin()方法)。假设您不能仅修复容器,那么下一个最佳选项是重载begin函数。Herb建议您总是使用begin函数来促进代码的一致性和一致性。不需要记住哪些容器支持方法开始,哪些需要函数开始。

As an aside, the next C++ rev should copy D's pseudo-member notation. If a.foo(b,c,d) is not defined it instead tries foo(a,b,c,d). It's just a little syntactic sugar to help us poor humans who prefer subject then verb ordering.

顺便说一句,下一个c++ rev应该复制D的伪成员表示法。如果a.foo(b,c,d)没有定义,而是尝试foo(a,b,c,d)。这只是一点点语法糖,帮助我们这些可怜的人,他们更喜欢主语而不是动词的顺序。

#4


15  

To answer your question, the free functions begin() and end() by default do nothing more than call the container's member .begin() and .end() functions. From <iterator>, included automatically when you use any of the standard containers like <vector>, <list>, etc., you get:

要回答您的问题,空闲函数begin()和end()默认情况下仅仅调用容器的成员.begin()和.end()函数。从 ,使用任何标准容器如 等时自动包含,得到:

template< class C > 
auto begin( C& c ) -> decltype(c.begin());
template< class C > 
auto begin( const C& c ) -> decltype(c.begin()); 

The second part of you question is why prefer the free functions if all they do is call the member functions anyway. That really depends on what kind of object v is in your example code. If the type of v is a standard container type, like vector<T> v; then it doesn't matter if you use the free or member functions, they do the same thing. If your object v is more generic, like in the following code:

第二个问题是如果*函数只调用成员函数,为什么要选择它呢?这取决于示例代码中对象v的类型。如果v的类型是标准容器类型,如向量 v;不管你用的是*函数还是成员函数,它们都是一样的。如果你的对象v更通用,如下面的代码所示:

template <class T>
void foo(T& v) {
  auto i = v.begin();     
  auto e = v.end(); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

Then using the member functions breaks your code for T = C arrays, C strings, enums, etc. By using the non-member functions, you advertise a more generic interface that people can easily extend. By using the free function interface:

然后使用成员函数破坏T = C数组、C字符串、枚举等的代码。使用免费功能界面:

template <class T>
void foo(T& v) {
  auto i = begin(v);     
  auto e = end(v); 
  for(; i != e; i++) { /* .. do something with i .. */ } 
}

The code now works with T = C arrays and C strings. Now writing a small amount of adapter code:

代码现在与T = C数组和C字符串一起工作。现在编写少量适配器代码:

enum class color { RED, GREEN, BLUE };
static color colors[]  = { color::RED, color::GREEN, color::BLUE };
color* begin(const color& c) { return begin(colors); }
color* end(const color& c)   { return end(colors); }

We can get your code to be compatible with iterable enums too. I think Herb's main point is that using the free functions is just as easy as using the member functions, and it gives your code backward compatibility with C sequence types and forward compatibility with non-stl sequence types (and future-stl types!), with low cost to other developers.

我们还可以让您的代码与可迭代枚举兼容。我认为Herb的主要观点是,使用*函数和使用成员函数一样简单,并且它使您的代码与C序列类型向后兼容,并与非stl序列类型(和future-stl类型!)向前兼容,对其他开发人员来说成本更低。

#5


4  

Whereas the non-member functions don't provide any benefit for the standard containers, using them enforces a more consistent and flexible style. If you at some time want to extend an existing non-std container class, you'd rather define overloads of the free functions, instead of altering the existing class's definition. So for non-std containers they are very useful and always using the free functions makes your code more flexible in that you can substitute the std container by a non-std container more easily and the underlying container type is more transparent to your code as it supports a much wider variety of container implementations.

而非成员函数并不能为标准容器提供任何好处,使用它们可以实现更一致和更灵活的风格。如果您有时想要扩展一个现有的非std容器类,您宁愿定义空闲函数的重载,而不是修改现有类的定义。所以对于non-std容器他们非常有用,并且随时使用免费功能使您的代码更加灵活,你可以用std non-std容器的容器更容易和底层容器类型更透明代码,因为它支持更广泛的各种各样的容器实现。

But of course this always has to be weighted properly and over abstraction is not good either. Although using the free functions is not that much of an over-abstraction, it nevertheless breaks compatibility with C++03 code, which at this young age of C++11 might still be an issue for you.

但是,当然,这总是需要适当地权衡,而过度抽象也不好。虽然使用免费函数并不是太过抽象,但是它破坏了与c++ 03代码的兼容性,在c++ 11这个年轻的时代,这对您来说仍然是一个问题。

#6


2  

One benefit of std::begin and std::end is that they serve as extension points for implementing standard interface for external classes.

std的一个好处是:开始和std::end是为外部类实现标准接口的扩展点。

If you'd like to use CustomContainer class with range-based for loop or template function which expects .begin() and .end() methods, you'd obviously have to implement those methods.

如果您想要使用基于区间的CustomContainer类,则希望使用.begin()和.end()方法,您显然必须实现这些方法。

If the class does provide those methods, that's not a problem. When it doesn't, you'd have to modify it*.

如果类确实提供了这些方法,那么这不是问题。如果没有,你必须修改它*。

This is not always feasible, for example when using external library, esspecially commercial and closed source one.

这并不总是可行的,例如在使用外部库时,特别是商业和封闭源代码库时。

In such situations, std::begin and std::end come in handy, since one can provide iterator API without modifying the class itself, but rather overloading free functions.

在这种情况下,std::begin和std::end就派上用场了,因为可以在不修改类本身的情况下提供迭代器API,而是重载*函数。

Example: suppose that you'd like to implement count_if function that takes a container instead of a pair of iterators. Such code might look like this:

示例:假设您希望实现count_if函数,它使用一个容器而不是一对迭代器。这样的代码可能是这样的:

template<typename ContainerType, typename PredicateType>
std::size_t count_if(const ContainerType& container, PredicateType&& predicate)
{
    using std::begin;
    using std::end;

    return std::count_if(begin(container), end(container),
                         std::forward<PredicateType&&>(predicate));
}

Now, for any class you'd like to use with this custom count_if, you only have to add two free functions, instead of modifying those classes.

现在,对于您想要使用这个定制count_if的任何类,您只需添加两个空闲函数,而不必修改这些类。

Now, C++ has a mechanisim called Argument Dependent Lookup (ADL), which makes such approach even more flexible.

现在,c++有了一个叫做参数依赖查找(ADL)的机制,这使得这种方法更加灵活。

In short, ADL means, that when a compiler resolves an unqualified function (i. e. function without namespace, like begin instead of std::begin), it will also consider functions declared in namespaces of its arguments. For example:

简而言之,ADL意味着,当编译器解析一个非限定函数(即没有名称空间的函数,比如begin而不是std::begin)时,它还将考虑在其参数的名称空间中声明的函数。例如:

namesapce some_lib
{
    // let's assume that CustomContainer stores elements sequentially,
    // and has data() and size() methods, but not begin() and end() methods:

    class CustomContainer
    {
        ...
    };
}

namespace some_lib
{    
    const Element* begin(const CustomContainer& c)
    {
        return c.data();
    }

    const Element* end(const CustomContainer& c)
    {
        return c.data() + c.size();
    }
}

// somewhere else:
CustomContainer c;
std::size_t n = count_if(c, somePredicate);

In this case, it doesn't matter that qualified names are some_lib::begin and some_lib::end - since CustomContainer is in some_lib:: too, compiler will use those overloads in count_if.

在这种情况下,限定名是否为some_lib: begin和some_lib: end——因为CustomContainer在some_lib:: too中,编译器将在count_if中使用这些重载。

That's also the reason for having using std::begin; and using std::end; in count_if. This allows us to use unqualified begin and end, therefore allowing for ADL and allowing compiler to pick std::begin and std::end when no other alternatives are found.

这也是使用std: begin的原因;和使用std::结束;在count_if。这允许我们使用非限定的开始和结束,因此允许ADL,并允许编译器选择std::begin和std::end,当没有找到其他替代时。

We can eat the cookie and have the cookie - i. e. have a way to provide custom implementation of begin/end while the compiler can fall back to standard ones.

我们可以吃这个cookie并拥有这个cookie——也就是说,我们可以提供开始/结束的自定义实现,而编译器可以返回到标准实现。

Some notes:

一些注意事项:

  • For the same reason, there are other similar functions: std::rbegin/rend, std::size and std::data.

    出于同样的原因,还有其他类似的功能:std::rbegin/rend, std::size和std::data。

  • As other answers mentions, std:: versions have overloads for naked arrays. That's useful, but is simply a special case of what I've described above.

    正如其他答案提到的,std::版本对裸数组有重载。这很有用,但只是我上面描述的一个特例。

  • Using std::begin and friends is particularly good idea when writing template code, because this makes those templates more generic. For non-template you might just as well use methods, when applicable.

    在编写模板代码时,使用std::begin和friends是一个非常好的主意,因为这使得这些模板更加通用。对于非模板,您可以在适当的时候使用方法。

P. S. I'm aware that this post is nearly 7 years old. I came across it because I wanted to answer a question which was marked as a duplicate and discovered that no answer here mentions ADL.

p。s。我知道这个帖子已经快7年了。我偶然发现了这个问题,因为我想回答一个被标记为重复的问题,并且发现这里没有答案提到ADL。