Invoking begin and end via using-directive?

后端 未结 4 1522
醉酒成梦
醉酒成梦 2021-02-19 01:32

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:54

    the documentation of swap specifies that the idiom you refer to is common practice in the stl library

    Many components of the standard library (within std) call swap in an unqualified manner to allow custom overloads for non-fundamental types to be called instead of this generic version: Custom overloads of swap declared in the same namespace as the type for which they are provided get selected through argument-dependent lookup over this generic version.

    No such thing is present in the documentation for begin and end.

    For this reason, you can definitely use the

    using std::begin;
    using std::end;
    some_algorithm(begin(some_container), end(some_container));
    

    calling convention, but you must be aware that this is a convention which doesn't apply to e.g. standard algorithms but to your code only.

    0 讨论(0)
  • 2021-02-19 01:57

    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.

    0 讨论(0)
  • 2021-02-19 02:09

    Using a using-declaration like that is the correct way IMO. It's also what the standard does with the range for loop: if there is no begin or end members present then it will call begin(x) and end(x) with std as an associated namespace (i.e. it will find std::begin and std::end if ADL doesn't find non-member begin and end).

    If you find that writing using std::begin; using std::end; all the time is tedious then you can use the adl_begin and adl_end functions below:

    namespace aux {
    
    using std::begin;
    using std::end;
    
    template<class T>
    auto adl_begin(T&& x) -> decltype(begin(std::forward<T>(x)));
    
    template<class T>
    auto adl_end(T&& x) -> decltype(end(std::forward<T>(x)));
    
    template<class T>
    constexpr bool is_array()
    {
        using type = typename std::remove_reference<T>::type;
        return std::is_array<type>::value;
    }
    
    } // namespace aux
    
    template<class T,
             class = typename std::enable_if<!aux::is_array<T>()>::type>
    auto adl_begin(T&& x) -> decltype(aux::adl_begin(std::forward<T>(x)))
    {
        using std::begin;
        return begin(std::forward<T>(x));
    }
    
    template<class T,
             class = typename std::enable_if<!aux::is_array<T>()>::type>
    auto adl_end(T&& x) -> decltype(aux::adl_end(std::forward<T>(x)))
    {
        using std::end;
        return end(std::forward<T>(x));
    }
    
    template<typename T, std::size_t N>
    T* adl_begin(T (&x)[N])
    {
        return std::begin(x);
    }
    
    template<typename T, std::size_t N>
    T* adl_end(T (&x)[N])
    {
        return std::end(x);
    }
    

    This code is pretty monstrous. Hopefully with C++14 this can become less arcane:

    template<typename T>
    concept bool Not_array()
    {
        using type = std::remove_reference_t<T>;
        return !std::is_array<type>::value;
    }
    
    decltype(auto) adl_begin(Not_array&& x)
    {
        using std::begin;
        return begin(std::forward<Not_array>(x));
    }
    
    decltype(auto) adl_end(Not_array&& x)
    {
        using std::end;
        return end(std::forward<Not_array>(x));
    }
    
    template<typename T, std::size_t N>
    T* adl_begin(T (&x)[N])
    {
        return std::begin(x);
    }
    
    template<typename T, std::size_t N>
    T* adl_end(T (&x)[N])
    {
        return std::end(x);
    }
    
    0 讨论(0)
  • 2021-02-19 02:09

    If your some_container is standard container, std:: prefix is needless

    #include <iostream>
    #include <vector>
    #include <algorithm>
    int main(){ 
           std::vector<int>v { 1, 7, 1, 3, 6, 7 };
           std::sort( begin(v), end(v) ); // here ADL search finds std::begin, std::end
    }
    
    0 讨论(0)
提交回复
热议问题