我发现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中实现条件复制和移动构造函数?
答案 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;