提问人:DWil 提问时间:10/10/2023 最后编辑:DWil 更新时间:10/11/2023 访问量:87
如何处理非平凡析构函数类型的可变参数模板构造函数
How do I handle variadic template constructors for non-trivial destructor types
问:
我正在编写一个容器,该容器的行为类似于 std::optional,但如果发生错误,则保留 ErrorType。
我在转发具有非平凡析构函数的类型的构造时遇到了问题 - 特别是可以用 .std::initializer_list
我在下面写了一个例子,并在其中列出了一些问题,我希望这些问题能解释我正在挣扎的问题。int main()
您可以看到,由于非平凡的析构函数,无法构造 a。Result<MyStructList>
#include <array>
#include <initializer_list>
#include <iostream>
#include <type_traits>
#include <utility>
#include <vector>
struct MyStruct {
MyStruct(int i, double d, const char *c) : i(i), d(d), c(c) {}
int i = 0;
double d = 10.0;
const char *c = "hello";
};
// Prove MyStruct is trivially destructible
static_assert(std::is_trivially_destructible_v<MyStruct>);
struct MyStructList {
std::vector<int> a;
};
// Prove MyStructList is NOT trivially destructible
static_assert(!std::is_trivially_destructible_v<MyStructList>);
// A Result class similar to Optional that can return ResultType or an
// ErrorType.
template <typename ResultType, typename ErrorType = const char *> class Result {
public:
// ----------------------------------------
// !! Construct a Result based on the ErrorType
// Constructor 1 - R-value reference construction
constexpr Result(ErrorType &&error) : error_(error), error_state_(true) {}
// Constructor 2 - L-value reference construction
constexpr Result(const ErrorType &error)
: error_(error), error_state_(true) {}
// ----------------------------------------
// ----------------------------------------
// !! Construct a Result based on the ResultType
// Constructor 3 - L-value reference construction
constexpr explicit Result(const ResultType &result) : result_(result) {}
// Constructor 4 - R-value reference construction
constexpr explicit Result(ResultType &&result) : result_(std::move(result)) {}
// ----------------------------------------
// ----------------------------------------
// Variadic template constructor
// Constructor 5 - Variadic template constructor to forward construction to
// the ResultType
template <typename... Args>
constexpr Result(Args &&...args)
requires std::constructible_from<ResultType, Args...>
: result_(std::forward<Args>(args)...) {}
// Constructor 6 - Variadic template constructor for initialiser list
template <typename Type, typename... Args>
constexpr Result(std::initializer_list<Type> list, Args &&...args)
requires std::constructible_from<ResultType, Args...>
: result_(list, std::forward<Args>(args)...) {}
// ----------------------------------------
// ----------------------------------------
// Copy constructor
// Constructor 7
constexpr Result(const Result &result)
: error_state_(result.error_state_){
if(error_state_){
error_ = result.error_;
} else {
result_ = result.result_;
}
}
// ----------------------------------------
// ----------------------------------------
// Move constructor
// Constructor 8
// Probably cheaper to just copy the error_state_ bool than move it
constexpr Result(Result &&result) : error_state_(result.error_state_) {
if (error_state_) {
error_ = std::move(result.error_);
} else {
result_ = std::move(result.result_);
}
}
// ----------------------------------------
// ----------------------------------------
// Assignment operators
// Copy assignment
Result& operator=(const Result& result) {
error_state_ = result.error_state_;
if(error_state_){
error_ = result.error_;
} else {
result_ = result.result_;
}
return *this;
}
// Non const copy assignment
Result& operator=(Result& result) {
error_state_ = result.error_state_;
if(error_state_){
error_ = result.error_;
} else {
result_ = result.result_;
}
return *this;
}
// Move assignment
Result& operator=(Result&& result) {
error_state_ = result.error_state_;
if(error_state_){
error_ = std::move(result.error_);
} else {
result_ = std::move(result.result_);
}
return *this;
}
ResultType &operator()() { return result_; }
private:
union {
ResultType result_;
ErrorType error_;
};
bool error_state_{false};
};
int main() {
// Uses Constructor 1
Result<MyStruct> object_1("Error"); // Compiles
// Use Constructor 2
const char *my_error = "Error";
Result<MyStruct> object_2(my_error); // Compiles
// Use Constructor 3
const MyStruct my_struct(0, 1.0, "Test3");
const Result<MyStruct> object_3(my_struct); // Compiles
//// Use Constructor 4
Result<MyStruct> object_4(MyStruct{0, 1.0, "Test4"}); // Compiles
// Result<MyStruct> object_5({0, 1.0, "Test5"}); // Doesn't compile, Is there
// a way to fix this? The compiler fails about ambiguity.
// Use Constructor 5
Result<MyStruct> object_6{0, 1.0, "Test6"}; // Compiles
// Result<MyStruct> object_7{0, 1.0, 1.0}; // Doesn't compile, as expected due
// to concept
// Use Constructor 6
// How to forward initialiser list construction. How to make this work?
// Result<MyStructList> object_8{{0,1,2}}; // Doesn't compile. Non trivial destructor is used, what's the problem here?
// Use Constructor 7
Result<MyStruct> object_7(object_6); // Compiles
std::cout << object_7().c << std::endl;
// Use Constructor 8
Result<MyStruct> object_8(std::move(object_6));
std::cout << object_8().c << std::endl;
// Use Const Copy assignment
Result<MyStruct> object_9{0, 1.0, "Test9"};
object_9 = object_3;
// Use Copy assignment
Result<MyStruct> object_10{0, 1.0, "Test9"};
object_10 = object_9;
// Use Move assignment
Result<MyStruct> object_11{0, 1.0, "Test10"};
object_11 = std::move(object_10);
return 0;
我正在 godbolt.org 上编译它x86_64 clang 17.0.1 和 -std=c++20 作为唯一的标志。
答:
2赞
Jarod42
10/11/2023
#1
构造函数 #5 “包括”构造函数 #3 和构造函数 #4。
因此,删除构造函数 3 和构造函数 4。这修复了 .object_5
使用了非平凡的析构函数,这里有什么问题?
您应该提供析构函数来处理非平凡的析构函数:
~Result() {
if (error_state_) {
error_.~ErrorType();
} else {
result_.~ResultType();
}
}
评论
0赞
DWil
10/11/2023
谢谢你@Jarod42。这正是我想要的。几个后续问题。删除更显式的构造函数 #3 和 #4 是否有任何问题。我没有意识到非平凡的析构函数的答案如此简单!这解决了我所有的疑问。总的来说,对这个容器有什么特别的评论吗?我是否遗漏了任何明显或不安全的东西?
0赞
Jarod42
10/11/2023
您也可以删除非常量复制分配。
评论
Result<MyStruct> object_9 = object_8;
不是赋值,而是复制的构造函数。Result<MyStruct> object_10 = std::move(object_9);
是移动构造,而不是分配。 始终是初始化,而不是赋值。T name = value