Invoking begin and end via using-directive?

后端 未结 4 2117
刺人心
刺人心 2021-02-19 01:44

The established idiom for invoking swap is:

using std::swap
swap(foo, bar);

This way, swap can be overloaded for user

4条回答
  •  遇见更好的自我
    2021-02-19 01:45

    Disclaimer: For the pedantic types (or pedants, if you want to be pedantic...), I generally refer to the word "overload" here as "Create functions that have the names begin and end and do using std::begin; using std::end;.", which, believe me, is not tedious for me to write at all, but is very hard to read and is redundant to read. :p.


    I'll basically give you the possible use-cases of such technique, and later my conclusion.

    Case 1 - Your begin and end methods do not act like those of the standard containers

    One situation where you may need to overload the std::begin and std::end functions is when you're using the begin and end methods of your type in a different way other than to provide iterator-like access to the elements of an object, and want to have overloads of std::begin and std::end call the begin and end methods used for iteration.

    struct weird_container {
       void begin() { std::cout << "Start annoying user." }
       void end() { std::cout << "Stop annoying user." }
    
       iterator iter_begin() { /* return begin iterator */ }
       iterator iter_end() { /* return end iterator */ }
    };
    
    
    auto begin(weird_container& c) {
       return c.iter_begin();
    }
    
    auto end(weird_container& c) {
       return c.iter_end();
    }
    

    However, you wouldn't and shouldn't do such a crazy thing as range-for would break if used with an object of weird_container, as per rules of range-for, the weird_container::begin() and weird_container::end() methods would be found before the stand-alone function variants.

    This case therefore brings an argument not to use what you have proposed, as it would break one very useful feature of the language.

    Case 2 - begin and end methods aren't defined at all

    Another case is when you don't define the begin and end methods. This is a more common and applicable case, when you want to extend your type to be iteratable without modifying the class interface.

    struct good_ol_type {
       ...
       some_container& get_data();
       ...
    };
    
    auto begin(good_ol_type& x) {
       return x.get_data().begin();
    }
    
    auto end(good_ol_type& x) {
       return x.get_data().end();
    }
    

    This would enable you to use some nifty features on good_ol_type (algorithms, range-for, etc) without actually modifying its interface! This is in line with Herb Sutter's recommendation of extending the functionality of types through non-member non-friend functions.

    This is the good case, the one where you actually want to overload std:;begin and std::end.

    Conclusion

    As I haven't ever seen someone do something like that of the first case (except for my example), then you'd really want to use what you've proposed and overload std::begin and std::end wherever applicable.


    I did not include here the case where you defined both begin and end methods, and begin and end functions that does different things than the methods. I believe such a situation is contrived, ill-formed and/or done by a programmer who haven't had much experience delving into the debugger or reading novel template errors.

提交回复
热议问题