Prefer some features over ADL

I want to use based on the for range to iterate over Unicode codes in UTF8 encoding std::string . I defined my own begin and end in the global namespace, but it is preferable to use begin and end tags in the std (i.e. Found ADLs). Is there a way to prefer my own features?

Example:

 const char* begin(const std::string& s) { std::cout << "BEGIN"; return s.data(); } const char* end(const std::string& s) { std::cout << "END"; return s.data() + s.length(); } int main() { std::string s = "asdf"; for (char c : s) std::cout << c; } 

I want him to print BEGINENDasdf (or ENDBEGINasdf ), but prints asdf .

Is there no other way than to make a for manual using a qualified name?

+7
c ++ argument-dependent-lookup
source share
3 answers

Wrap std::string in your own type. Once you create a template, you can customize any existing container and add your own range logic to it. This is not even what is different from your first attempt.

 #include <string> #include <iostream> template <typename S> struct custom_container { S &s_; custom_container (S &s) : s_(s) {} auto begin() -> decltype(s_.begin()) { std::cout << "BEGIN"; return s_.begin(); } auto end() -> decltype(s_.end()) { std::cout << "END"; return s_.end(); } }; template <typename S> custom_container make_container (S &s) { return custom_container <S> (s); } int main () { std::string t = "asdf"; auto s = make_container(t); for (char c : s) { std::cout << c; } } 

Outputs

BEGINENDasdf

+6
source share

N3337 6.5.4 / 1:

(...) begin-expr and end-expr are defined as follows:

- if _RangeT is the type of the array, begin-expr and end-expr are __range and __range + __bound , respectively, (...);

- if _RangeT is the type of the class, unquali fied-id s begin and end are looked at the class _RangeT in the class, as if access to a class member (3.4.5), and if either (or both) find at least one declaration, begin-expr and end-expr are __range.begin() and __range.end() , respectively;

- otherwise, begin-expr and end-expr are begin(__range) and end(__range) , respectively, where begin and end looked up with an argument-dependent search (3.4.2). For the purposes of this name, lookup, namespace std is the associated namespace.

In other words, it will call the member functions std::string begin and end (second bullet list). The correct solution is to provide a wrapper class, as anthony suggests .

Note. If you use -std=c++1y , you can omit the decltype target.

You can also write typedef to make it less typical:

 typedef custom_string<std::string> cs; for (char c : cs(t)) { std::cout << c; } 
+6
source share

The cleanest way, at least at the time of use, is to specify your type for a special iteration.

Firstly, some mechanisms:

 template<class Mark, class T> struct marked_type { T raw; marked_type(T&& in):raw(std::forward<T>(in)) {} }; template<typename Mark, typename T> marked_type<Mark, T> mark_type( T&& t ) { return {std::forward<T>(t)}; } 

Next, we invent a sign that says “iterate weird,” and the overload starts / ends:

 struct strange_iteration {}; template<typename T> auto begin( marked_type<strange_iteration, T> const& container ) -> decltype( std::begin(std::forward<T>(container.raw)) ) { std::cout << "BEGIN"; using std::begin; return begin(std::forward<T>(container.raw)); } template<typename T> auto end( marked_type<strange_iteration, T> const& container ) -> decltype( std::end(std::forward<T>(container.raw)) ) { std::cout << "END"; using std::end; return end(std::forward<T>(container.raw)); } 

and then at the point of use:

 std::string s = "hello world"; for( char c : mark_type<strange_iteration>(s) ) { std::cout << c; } std::cout << "\n"; 

with one entry that I wrote mark_type to be too general.

Now mark_type<Foo> will create links to lvalues ​​and create a moved copy of rvalue if it is passed to it. In an iteration, the lifetime of the return value will be increased by extending the life of the link.

You can use this technique to do something like

 for( char c : mark_type<reverse_iteration>(s) ) 

where are we instead iterating back now, regardless of the container we passed into. A “create copy” for rvalue is required for such constructs:

 for( char c: mark_type<reverse_iteration>(mark_type<strange_iteration>(s)) 

where we connect the labels. The extension of the service life extends only to the most remote return value, and our "create a copy and move" by rvalue is basically an extension of the service life manually.

Finally, the use of std::begin in the above code is best done in the context of ADL tolerance in return values. Create a helper namespace as follows:

 namespace adl_helper { using std::begin; using std::end; template<typename T> auto adl_begin(T&& t)->decltype( begin(std::forward<T>(t)) ); // no implementation template<typename T> auto adl_end(T&& t)->decltype( end(std::forward<T>(t)) ); // no implementation // add adl_cbegin, adl_rbegin etc in C++14 } 

then replace std::begin with decltype in my previous code with adl_helper::adl_begin , which emulates how for( a:b ) loops find begin and end touch better (not perfect, but better).

C ++ 1y may come with some machines to remove the need for the aforementioned hack.

Code execution example: http://ideone.com/RYvzD0

+1
source share

All Articles