2

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 uses push_back:

#include <iterator>
#include <vector>
#include <iostream>

template<class Container>
class back_emplace_iterator : public std::iterator< std::output_iterator_tag,
                                                   void, void, void, void >
{
protected:
    Container* container;
public:
    typedef Container container_type;

    explicit back_emplace_iterator(Container& x) : container(&x) {}

    // ==== FROM UPDATE ====
    template<class T>
    using _not_self =
        typename std::enable_if<
            !std::is_same<
                typename std::decay<T>::type,
                back_emplace_iterator
            >::value
        >::type;
    // =====================

    // ==== UNIVERSAL REFERENCE ASSIGNMENT ====
    template<class T, class = _not_self<T>>
    back_emplace_iterator<Container>&
    operator=(T&& t)
    {
        container->emplace_back(std::forward<T>(t));
        return *this;
    }
    // ========================================

    back_emplace_iterator& operator*() { return *this; }
    back_emplace_iterator& operator++() { return *this; }
    back_emplace_iterator& operator++(int) { return *this; }
};

template< class Container >
inline back_emplace_iterator<Container>
back_emplacer( Container& c )
{
    return back_emplace_iterator<Container>(c);
}

struct Demo
{
    int i;
    Demo(int i) : i(i) {}
};

int main()
{
    std::vector<int> x = {1,2,3,4,5};

    std::vector<Demo> y;

    std::copy(x.begin(), x.end(), back_emplacer(y));

    for (auto d : y)
        std::cout << d.i << std::endl;
}

Does the universal reference operator=(T&&) defeat generation of a default copy assignment operator and/or default move assignment operator?

If so, how can they be explicitly defined so that they will beat the universal reference version in overload resolution?

If not, do the implicitly generated ones beat the universal reference version?

Also, will the universal reference version work appropriately with initializer lists?

Update:

Added alias template _not_self to restore default copy/move assignment. Thanks Alex.

Community
  • 1
  • 1
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • It occurs to me that that is a very poorly designed iterator... Use a proxy class. – Mooing Duck Sep 10 '13 at 20:46
  • @MooingDuck: What do you mean? Proxy class for what? The design is the same as the libstdc++ version of `back_inserter`, except for the forwarding universal reference `operator=`. – Andrew Tomazos Sep 10 '13 at 20:46
  • 1
    You can "restore" the usual copy/move by adding a default template parameter to your universal assignment operator: `class = typename std::enable_if<!std::is_same::type, back_emplace_iterator>::value>::type` – Alex Sep 10 '13 at 20:51
  • @Alex: I think, as per Kerreks answer, this is unnecessary. The implicitly-declared copy/move will already be the best match when appropriate. Or do you have a case when they are not? – Andrew Tomazos Sep 10 '13 at 20:53
  • 2
    @user1131467: The universal reference operator is a better match if you assign a `back_emplacer&` (non-const) to another `back_emplacer` – Alex Sep 10 '13 at 20:57
  • @Alex: Yes, you're right. This is what I was worried about originally. – Andrew Tomazos Sep 10 '13 at 21:20
  • @Alex: I added an alias template `_not_self` as per your suggestion. Thanks. – Andrew Tomazos Sep 10 '13 at 21:33
  • @Alex: If you want to change your comment to an answer, I'll accept it. – Andrew Tomazos Sep 10 '13 at 21:33
  • @user1131467 Thanks. I tried to answer the remaining question... – Alex Sep 11 '13 at 15:50

2 Answers2

3

Just as for copy-constructors, copy-assignment operators are not templates (12.8/17):

A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X...

You still get the implicitly-declared copy-assignment and move-assignment operators, and they are im­pli­cit­ly-defined if odr-used, and they participate in overload resolution (so if your arguments match X const & or X && precisely, those will be preferred over the templated operator=).

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

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<T>) 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<bool Condition>
using EnableIf = typename std::enable_if<Condition>::type;

template<bool Condition>
using DisableIf = typename std::enable_if<!Condition>::type;

struct HasValueTypeImpl
{
    template<class T>
    static auto test(T&&) -> decltype( std::declval<typename T::value_type>(), std::true_type() );
    static auto test(...) -> std::false_type;
};

template<class T>
using HasValueType = decltype( HasValueTypeImpl::test(std::declval<T>()) );

template<class T, class U>
using IsConstructible = typename std::is_constructible<T, U>::type;

template<class Container>
class back_emplace_iterator
    : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
    template<class T>
    using IsSelf = typename std::is_same< typename std::decay<T>::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<T>::value >
    >
    back_emplace_iterator& operator =(T&& t)
    {
        container->emplace_back(std::forward<T>(t));
        return *this;
    }

    // 2
    template<
        class T = typename Container::value_type,
        class = EnableIf<
            HasValueType<T>::value &&
            IsConstructible<T, std::initializer_list<typename T::value_type>>::value
        >
    >
    back_emplace_iterator& operator =(std::initializer_list<typename T::value_type> 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<class Container>
inline back_emplace_iterator<Container> back_emplacer(Container& c) {
    return back_emplace_iterator<Container>(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<std::string> x = {"1","2"};
    std::vector<std::vector<std::string>> 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...

Alex
  • 709
  • 3
  • 10