Я обнаружил, что мой 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 без специализаций или большого количества специализаций?