具有专门性的模板类中可靠的条件复制和移动构造函数

时间:2018-10-03 21:06:55

标签: c++ c++17 copy-constructor template-specialization move-constructor

我发现compressed_tuple<T1, T2>T1没有复制和/或移动构造函数且错误“试图引用时,T2类没有编译已删除的功能” 。该错误是指在compressed_tuple的副本构造函数中使用了已删除的副本构造函数。我知道错误是什么,为什么得到它,以及如何解决它。我不明白的是,如何在没有过多专业化的情况下做到这一点(下文详述)。

compressed_tuple是我的一对名字,当至少一个值是空且可派生时,它通过专门使用EBO来最小化其大小。即std::unique_ptr通常是用这种机制实现的,以防止空的删除程序影响其大小(否则,普通的std::unique_ptr会大于指针)。

TL:DR 跳到底部,我在这里询问实际问题。导致一切的都是信息环境。

通常,我会使用专业化并将其称为一天,类似于:

template<class T>
constexpr bool is_copyable_v = 
    std::is_copy_constructible_v<T> && std::is_copy_assignable_v<T>;
template<class T>
constexpr bool is_movable_v =
    std::is_move_constructible_v<T> && std::is_move_assignable_v<T>;

template<class T>
class example_base
{
public:
    T value;

protected:
    example_base() = default;
    example_base(const example_base&) = default;
    example_base(example_base&&) = default;
    ~example_base() = default;

    inline example_base& operator=(const example_base& source)
        noexcept(std::is_nothrow_copy_assignable_v<T>)
    {
        static_assert(is_copyable_v<T>, "T must be copyable.");

        if constexpr (is_copyable_v<T>) {
            value = source.value;
        }
        return *this;
    }
    inline example_base& operator=(example_base&& source)
        noexcept(std::is_nothrow_move_assignable_v<T>)
    {
        static_assert(is_movable_v<T>, "T must be movable.");

        if constexpr (is_movable_v<T>) {
            value = std::move(source.value);
        }
        return *this;
    }
};

// T is both copyable and movable.
template<
    class T,
    bool = is_copyable_v <T>,
    bool = is_movable_v<T>>
class example final : example_base<T>
{
    using base = example_base<T>;

public:
    example() = default;
    inline example(const example& source)
        noexcept(std::is_nothrow_copy_constructible_v<T>) :
        base(source) {}
    inline example(example&& source)
        noexcept(std::is_nothrow_move_constructible_v<T>) :
        base(std::move(source)) {}

    inline example& operator=(const example& source)
        noexcept(std::is_nothrow_copy_assignable_v<T>)
    {
        return static_cast<example&>(base::operator=(source));
    }
    inline example& operator=(example&& source)
        noexcept(std::is_nothrow_move_assignable_v<T>)
    {
        return static_cast<example&>(base::operator=(std::move(source)));
    }
};

// T is copyable, but not movable.
template<class T>
class example<T, true, false> final : public example_base<T>
{
    using base = example_base<T>;

public:
    example() = default;
    inline example(const example& source)
        noexcept(std::is_nothrow_copy_constructible_v<T>) :
        base(source) {}
    example(example&&) = delete;

    inline example& operator=(const example& source)
        noexcept(std::is_nothrow_copy_assignable_v<T>)
    {
        return static_cast<example&>(base::operator=(source));
    }
    example& operator=(example&&) = delete;
};

// T isn't copyable, but is movable.
template<class T>
class example<T, false, true> final : public example_base<T>
{
    using base = example_base<T>;

public:
    example() = default;
    inline example(example&& source)
        noexcept(std::is_nothrow_move_constructible_v<T>) :
        base(std::move(source)) {}
    example(const example&) = delete;

    inline example& operator=(example&& source)
        noexcept(std::is_nothrow_move_assignable_v<T>)
    {
        return static_cast<example&>(base::operator=(std::move(source)));
    }
    example& operator=(const example&) = delete;
};

// T is neither copyable nor movable.
template<class T>
class example<T, false, false> final : public example_base<T>
{
public:
    example() = default;
    example(const example&) = delete;
    example(example&&) = delete;

    example& operator=(const example&) = delete;
    example& operator=(example&&) = delete;
};

这可以很好地工作,但是如果任何其他模板参数需要进一步的专业化,则会以指数形式爆炸。

compressed_tuple的所有专业知识都很丰富,因此我省略了大部分:

// T1 is empty and inheritable, but T2 isn't, so derive from T1 and store T2.
// Handles both <..., true, true> and <..., true, false>.
template<class T1, class T2, 
    bool = std::is_empty_v<T1> && !std::is_final_v<T1>,
    bool = std::is_empty_v<T2> && !std::is_final_v<T2>>
class compressed_tuple final : private T1 
{
private:
    using base = T1;

    T2 second;

public:
    compressed_tuple(const compressed_tuple& source)
        noexcept(
            std::is_nothrow_copy_constructible_v<T1> &&
            std::is_nothrow_copy_constructible_v<T2>) :
        base(source),
        second(source.second) {}
    /*...*/
};

// T2 is empty and inheritable, but T1 isn't, so derive from T2 and store T1.
template<class T1, class T2>
class compressed_tuple<T1, T2, false, true> final : private T2 
{
private:
    using base = T2;

    T1 first;

public:
    compressed_tuple(const compressed_tuple& source)
        noexcept(
            std::is_nothrow_copy_constructible_v<T1> &&
            std::is_nothrow_copy_constructible_v<T2>) :
        base(source),
        first(source.first) {}
    /*...*/
};

// Neither T1 nor T2 are empty and derivable, so store both.
template<class T1, class T2>
class compressed_tuple<T1, T2, false, false> final
{
private:
    T1 first;
    T2 second;

public:
    compressed_tuple(const compressed_tuple& source)
        noexcept(
            std::is_nothrow_copy_constructible_v<T1> &&
            std::is_nothrow_copy_constructible_v<T2>) :
        first(source.first),
        second(source.second) {}
    /*...*/
};

我要完成的工作可以通过以下方式实现:

template<
    class T,
    bool = is_copyable_v<T>,
    bool = is_movable_v<T>,
    bool = std::is_empty_v<T1> && !std::is_final_v<T1>,
    bool = std::is_empty_v<T2> && !std::is_final_v<T2>>
class compressed_tuple final { /*...*/ };

// ...specialize on all valid combinations...

尽管这将需要大量的专业知识。如果可能的话,我正在寻找替代方案。

据我所知,SFINEE并非对此的选择。 C ++ 20约束将解决这个问题,但是在撰写本文时,主流编译器要符合C ++ 20尚需时日。在没有专门知识或大量专门知识的情况下,如何在C ++ 17中实现条件复制和移动构造函数?

1 个答案:

答案 0 :(得分:3)

两种常见方法是:

  • 默认特殊成员函数,然后安排将其删除(例如,为此目的使用特殊的基类)。
  • “埃里克的把戏”:

    Foo& operator=(std::conditional_t<can_copy, Foo, nonesuch> const& rhs) {
        // implement
    }
    
    Foo& operator=(std::conditional_t<!can_copy, Foo, nonesuch> const&) = delete;