
1. Introduction
Previously, we analyzed and provided examples of ADL (Argument Dependent Lookup) in template-related technologies. However, in practical applications, if the iterator is a dangerous pointer type, it will not perform ADL actions. But if it is similar to a template projected type, ADL operations will occur. In the case of the Ranges library, due to the lack of ADL protection, it can lead to exceptions in certain situations. Therefore, in C++26, projected has been redefined to fix this issue, ensuring that its associated entities no longer include its template parameters. Generally, most operations on raw pointers do not trigger ADL behavior. For example, in the following case:
Holder<Incomplete> *a[10] = {}; // ten null pointers
Holder<Incomplete> **p = a; // OK
p += 1; // OK
assert(*p == nullptr); // OK
assert(p == a+1); // OK
assert(std::count(a, a+10, nullptr) == 10); // OK
2. Issues and Solutions in C++20
Let’s look at an example from Ranges in C++20:
template<class T> struct Holder { T t; };
struct Incomplete;
Holder<Incomplete> *a[10] = {}; // ten null pointers
assert(std::count(a, a+10, nullptr) == 10); // normal
assert(std::ranges::count(a, a+10, nullptr) == 10); // hard error
The reason for the error in this code lies in indirectly_comparable, T, Pred> which utilizes all associated types of projected, and T includes Holder. To address this issue, C++26 introduces a solution by implementing ADL protection, preventing T from becoming an associated type. This results in an ADL-proof std::projected.
template<class Associated>
struct projected { };
// Replace the above projected with the following:
template<class T>
struct __projected_impl {
struct type { };
};
template<class NonAssociated>
using projected = __projected_impl<NonAssociated>::type;
ADL will associate base classes of derived classes but will not associate nested classes with their containing classes, thus __projected_impl::type acts as a firewall against unwanted ADL.
3. Another Technical Implementation
Additionally, some proposals include a technique called “phrase of power”. If this technique can be applied, it may replace the associated implementation of projected. Essentially, it involves adding specific markers in the template header of class templates, indicating that arguments are not associated entities. If this technique can be utilized, related applications could use the following approach:
using T = Holder<Incomplete>*;
static_assert(std::equality_comparable<T>); // normal
static_assert(std::indirectly_comparable<T*, T*, std::equal_to<>>); // will be normal
static_assert(std::sortable<T*>); // will be normal
int main() {
Holder<Incomplete> *a[10] = {}; // ten null pointers
assert(std::count(a, a+10, nullptr) == 10); // normal
assert(std::ranges::count(a, a+10, nullptr) == 10); // will be normal
}
4. Conclusion
From these details, similar to previous standards, C++26 continues to repay the technical debt left behind. With both functional improvements and new technical solutions, these are important driving forces for the continuous development of C++. Our learning should also align with the evolution of the standard, filling gaps and continuously improving.