0

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

1 Answer 1

0

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_));
    }
  }
  // ----------------------------------------
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.