Надежное условное копирование и перемещение конструкторов в шаблонном классе со специализациями - PullRequest
0 голосов
/ 04 октября 2018

Я обнаружил, что мой 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...

Хотя это потребует большого количества специализаций.То, что я ищу, является альтернативой, если это возможно.

Насколько я понимаю, SFINAE не подходит для этого.Ограничения C ++ 20 решат эту проблему, но на момент написания этой статьи пройдет достаточно времени, прежде чем основные компиляторы станут совместимыми с C ++ 20.Как можно реализовать конструкторы условного копирования и перемещения в C ++ 17 без специализаций или большого количества специализаций?

1 Ответ

0 голосов
/ 04 октября 2018

Два общих подхода:

  • По умолчанию специальные функции-члены, а затем организовать их удаление (например, с помощью специального базового класса для этой цели).
  • "Прикол Эрика":

    Foo& operator=(std::conditional_t<can_copy, Foo, nonesuch> const& rhs) {
        // implement
    }
    
    Foo& operator=(std::conditional_t<!can_copy, Foo, nonesuch> const&) = delete;
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...