`back_emplacer` implementation: default operator= vs universal reference version?

前端 未结 2 1750
悲&欢浪女
悲&欢浪女 2021-01-22 18:18

In the context of this question, here is an implementation of a C++11 back_emplacer that uses emplace_back instead of how std::back_inserter

2条回答
  •  刺人心
    刺人心 (楼主)
    2021-01-22 19:05

    Your first question has been answered by Kerek SB. The second and third questions have been answered in the comments and in the update to your question.

    Using initializer lists with the current version of your iterator won't work, though. If you write something like *I = {1,2,3}, where I is of type back_emplace_iterator, the compiler will try to construct a new back_emplace_iterator using brace-initialization (if that's the right wording...) and that will fail. Just adding operator=(std::initializer_list) might not work in all cases. I think it's better to make this operator available if T = typename Container::value_type::value_type and if the containers value_type is constructible from such an initializer list.

    Here's what I'm trying to say:

    template
    using EnableIf = typename std::enable_if::type;
    
    template
    using DisableIf = typename std::enable_if::type;
    
    struct HasValueTypeImpl
    {
        template
        static auto test(T&&) -> decltype( std::declval(), std::true_type() );
        static auto test(...) -> std::false_type;
    };
    
    template
    using HasValueType = decltype( HasValueTypeImpl::test(std::declval()) );
    
    template
    using IsConstructible = typename std::is_constructible::type;
    
    template
    class back_emplace_iterator
        : public std::iterator
    {
        template
        using IsSelf = typename std::is_same< typename std::decay::type, back_emplace_iterator >::type;
    
        Container* container;
    
    public:
        typedef Container container_type;
    
        explicit back_emplace_iterator(Container& x) : container(&x)
        {
        }
    
        // 1
        template<
            class T,
            class = DisableIf< IsSelf::value >
        >
        back_emplace_iterator& operator =(T&& t)
        {
            container->emplace_back(std::forward(t));
            return *this;
        }
    
        // 2
        template<
            class T = typename Container::value_type,
            class = EnableIf<
                HasValueType::value &&
                IsConstructible>::value
            >
        >
        back_emplace_iterator& operator =(std::initializer_list ilist)
        {
            container->emplace_back(ilist);
            return *this;
        }
    
        // 3
        back_emplace_iterator& operator =(typename Container::value_type&& t)
        {
            container->emplace_back(std::move(t));
            return *this;
        }
    
        back_emplace_iterator& operator *() { return *this; }
        back_emplace_iterator& operator ++() { return *this; }
        back_emplace_iterator& operator ++(int) { return *this; }
    };
    
    template
    inline back_emplace_iterator back_emplacer(Container& c) {
        return back_emplace_iterator(c);
    }
    

    I have added a third assignment operator taking an rvalue-reference to a Container::value_type. This let's you assign even more things to your iterator, but this obviously moves this value into the container instead of constructing it in-place. So you might want to delete number 3.

    Here is a simple test case. The comments describe the assignment operator being used and the resulting vector.

    int main()
    {
        std::vector x = {"1","2"};
        std::vector> vec;
    
        auto I = back_emplacer(vec);
    
        *I++ = x;                                           // 1: ["1", "2"]
        *I++ = {x.begin(), x.end()};                        // 3: ["1", "2"]
        *I++ = {5, "xx"};                                   // 3: ["xx", "xx", "xx", "xx", "xx"]
        *I++ = {"eins", "zwei"};                            // 2: ["eins", "zwei"]
        *I++ = {"a", {'b', 'b', 'b'}, std::string("c")};    // 2: ["a", "bbb", "c"]
        *I++ = std::move(x);                                // 3: ["1", "2"]
    
        std::cout << support::pretty(vec) << "\n";
    }
    

    In these simple cases it's almost the same as you would get if you constructed the vectors using the given arguments (using brace-initialization).

    I'm not sure if everything works as expected...

提交回复
热议问题