`back_emplacer`实现:默认运营商= vs通用参考版本?

时间:2013-09-10 20:32:47

标签: c++ c++11

this question的上下文中,这是使用back_emplacer而不是emplace_back使用std::back_inserter的方式的C ++ 11 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;
}

通用引用operator=(T&&)是否会失败生成默认的复制赋值运算符和/或默认的移动赋值运算符?

如果是这样,它们如何被明确定义,以便它们在重载分辨率中击败通用参考版本?

如果没有,隐式生成的那些是否超过通用参考版本?

此外,通用参考版本是否适用于初始化列表?

更新

添加了别名模板_not_self以恢复默认的复制/移动分配。谢谢Alex。

2 个答案:

答案 0 :(得分:3)

就像复制构造函数一样,复制赋值运算符不是模板(12.8 / 17):

  

用户声明的副本分配运算符X::operator=是类X的非静态非模板成员函数...

你仍然得到隐式声明的复制赋值和移动赋值运算符,如果使用了odr,它们会被隐式定义,并且它们参与重载解析(所以如果你的参数匹配X const &或{{ 1}}确切地说,这些将优于模板X &&)。

答案 1 :(得分:2)

Kerek SB已回答您的第一个问题。第二和第三个问题已在评论和更新问题中得到解答。

但是,使用初始化程序列表和当前版本的迭代器将不起作用。如果您编写类似*I = {1,2,3}的内容,其中I的类型为back_emplace_iterator,则编译器将尝试使用大括号初始化来构造新的back_emplace_iterator(如果这是正确的措辞。 ..)那将失败。只是添加operator=(std::initializer_list<T>)可能并不适用于所有情况。我认为如果T = typename Container::value_type::value_type并且容器value_type可以从这样的初始化列表中构造,那么最好使这个运算符可用。

这就是我想说的:

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);
}

我添加了第三个赋值运算符,它对Container::value_type进行了右值引用。这让你为迭代器分配更多的东西,这显然将这个值移动到容器中而不是就地构造它。所以你可能想要删除3号。

这是一个简单的测试用例。注释描述了正在使用的赋值运算符和结果向量。

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";
}

在这些简单的情况下,如果使用给定的参数构造向量(使用大括号初始化),它几乎与您获得的相同。

我不确定一切是否按预期工作......