I'm close to finishing my container but my last problem to solve is how to handle the copy/move constructor and appropriately construct the correct member variable inside the private union member variable. I want my copy and move constructor to only construct the appropriate type in the union depending on a bool error_state_
so that the types don't have to have a default constructor.
The container code is below including the concepts I use. If you check the copy/move constructor you can see what I'm attempting to do with the private tagged constructors.
template<typename ResultType, typename ErrorType>
concept move_assignable = std::is_move_assignable_v<ResultType> && std::is_move_assignable_v<ErrorType>;
template<typename ResultType, typename ErrorType>
concept copy_assignable = std::is_copy_assignable_v<ResultType> && std::is_copy_assignable_v<ErrorType>;
template<typename ResultType, typename ErrorType>
concept copy_constructible = std::is_copy_constructible_v<ResultType> && std::is_move_constructible_v<ErrorType>;
template<typename ResultType, typename ErrorType>
concept move_constructible = std::is_move_constructible_v<ResultType> && std::is_move_constructible_v<ErrorType>;
// This concept aims to avoid the variadic template constructors being picked
// over copy and move constructors.
template<typename ResultType, typename... Args>
concept exclude_result_type = (sizeof...(Args) != 1 && std::constructible_from<ResultType, Args...>)
|| (sizeof...(Args) == 1 && std::constructible_from<ResultType, Args...>
&& !std::is_same_v<ResultType, std::decay_t<Args>...>);
template<typename ErrorType, typename... Args>
concept exclude_error_type = (sizeof...(Args) != 1 && std::constructible_from<ErrorType, Args...>)
|| (sizeof...(Args) == 1 && std::constructible_from<ErrorType, Args...>
&& !std::is_same_v<ErrorType, std::decay_t<Args>...>);
// A Result class similar to Optional that can return ResultType or an
// ErrorType.
template<typename ResultType, typename ErrorType = Error> class Result
{
public:
// ----------------------------------------
// !! Construct a Result based on the ErrorType
// R-value reference construction
constexpr explicit Result(ErrorType&& error) : error_{ std::move(error) }, error_state_(true) {}
// L-value reference construction
constexpr explicit Result(const ErrorType& error) : error_{ error }, error_state_(true) {}
// ----------------------------------------
// ----------------------------------------
// !! Construct a valid Result based on the ResultType
// R-value reference construction
constexpr explicit Result(ResultType&& result) : result_{ std::move(result) } {}
// L-value reference construction
constexpr explicit Result(const ResultType& result) : result_{ result } {}
// ----------------------------------------
// ----------------------------------------
// Variadic template constructors
template<typename... Args>
constexpr Result(Args&&... args)
requires exclude_result_type<ResultType, Args...>
: result_{ std::forward<Args>(args)... }
{}
template<typename... Args>
constexpr Result(Args&&... args)
requires exclude_error_type<ErrorType, Args...>
: error_{ std::forward<Args>(args)... }
{}
template<typename Type, typename... Args>
constexpr Result(std::initializer_list<Type> list, Args&&... args)
requires exclude_result_type<ResultType, Args...>
: result_{ list, std::forward<Args>(args)... }
{}
template<typename Type, typename... Args>
constexpr Result(std::initializer_list<Type> list, Args&&... args)
requires exclude_error_type<ErrorType, Args...>
: error_{ list, std::forward<Args>(args)... }
{}
// ----------------------------------------
// ----------------------------------------
// Copy constructor
constexpr Result(const Result& result) noexcept
requires copy_constructible<ResultType, ErrorType>
: Result(result.error_state_ ? Result{ Tag<ErrorType>(), result } : Result{ Tag<ResultType>(), result })
{
// if (error_state_) {
// error_ = result.error_;
// } else {
// result_ = result.result_;
// }
}
// ----------------------------------------
// ----------------------------------------
// Move constructor
constexpr Result(Result&& result) noexcept
requires move_constructible<ResultType, ErrorType>
: Result(result.error_state_ ? Result{ Tag<ErrorType>{}, std::move(result) }
: Result{ Tag<ResultType>{}, std::move(result) })
// : error_state_(result.error_state_)
{
// if (error_state_) {
// error_ = std::move(result.error_);
// } else {
// result_ = std::move(result.result_);
// }
}
// ----------------------------------------
// ----------------------------------------
// Destructor
~Result()
{
if (error_state_) {
// cppcheck-suppress ignoredReturnValue
error_.~ErrorType();
} else {
// cppcheck-suppress ignoredReturnValue
result_.~ResultType();
}
}
// ----------------------------------------
// ----------------------------------------
// 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;
}
// 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;
}
constexpr bool operator==(const Result<ResultType, ErrorType>& rhs) const
{
if (rhs.error_state_ == error_state_) {
if (error_state_) {
return rhs.error_ == error_;
} else {
return rhs.result_ == result_;
}
} else {
return false;
}
}
constexpr explicit operator bool() const noexcept { return !error_state_; }
// Operator overload for dereferencing.
// Not safe to do unless calling Good() before hand.
[[nodiscard]] constexpr ResultType& operator*() noexcept
{
assertm(!error_state_, "Attempting to retrieve value when Result is in error!");
return result_;
}
// Operator overload for dereferencing.
// Not safe to do unless calling Good() before hand.
[[nodiscard]] constexpr ResultType* operator->() noexcept
{
assertm(!error_state_, "Attempting to retrieve value when Result is in error!");
return &result_;
}
[[nodiscard]] constexpr const ResultType& Value() const noexcept
{
assertm(!error_state_, "Attempting to retrieve value when Result is in error!");
return result_;
}
[[nodiscard]] constexpr bool Good() const noexcept { return !error_state_; }
[[nodiscard]] constexpr bool Bad() const noexcept { return error_state_; }
// Not safe to call unless Bad() previously called.
[[nodiscard]] constexpr const ErrorType& Error() const noexcept { return error_; }
private:
template<typename T> struct Tag
{
};
constexpr Result([[maybe_unused]] Tag<ErrorType> tag, const Result& result) noexcept
: error_{ result.error_ }, error_state_(true)
{}
constexpr Result([[maybe_unused]] Tag<ResultType> tag, const Result& result) noexcept : result_{ result.result_ } {}
constexpr Result([[maybe_unused]] Tag<ErrorType> tag, Result&& result) noexcept
: error_{ std::move(result.error_) }, error_state_(true)
{}
constexpr Result([[maybe_unused]] Tag<ResultType> tag, Result&& result) noexcept
: result_{ std::move(result.result_) }
{}
union {
ResultType result_;
ErrorType error_;
};
bool error_state_{ false };
};
If I remove the delegated constructors and just use the commented move/copy assignment in those constructors everything works fine but doesn't use the appropriate constructor of the templated type.
Godbolt link to a demo: https://godbolt.org/z/xvvx4zT4s
I fixed this by removing the tagged delegating constructors and using placement new constructors instead.
// ----------------------------------------
// Copy constructor
constexpr Result(const Result& result) noexcept
requires copy_constructible<ResultType, ErrorType>
: error_state_{ result.error_state_ }
{
if (error_state_) {
new (&error_) ErrorType(result.error_);
} else {
new (&result_) ResultType(result.result_);
}
}
// ----------------------------------------
// ----------------------------------------
// Move constructor
constexpr Result(Result&& result) noexcept
requires move_constructible<ResultType, ErrorType>
: error_state_{ result.error_state_ }
{
if (error_state_) {
new (&error_) ErrorType(std::move(result.error_));
} else {
new (&result_) ResultType(std::move(result.result_));
}
}
// ----------------------------------------