c++ 如何使用委托构造函数和条件初始化器列表编写复制/移动构造函数

oxf4rvwz  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(106)

我即将完成我的容器,但我要解决的最后一个问题是如何处理复制/移动构造函数,并在私有联合成员变量中正确地构造成员变量。我希望我的复制和移动构造函数只构造联合体中依赖于bool error_state_的适当类型,这样类型就不必有默认构造函数。
下面是容器代码,包括我使用的概念。如果你检查复制/移动构造函数,你可以看到我试图用私有标记构造函数做什么。

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 };
};

如果我删除委托构造函数,只在这些构造函数中使用注解的移动/复制赋值,一切正常,但没有使用模板类型的适当构造函数。
Godbolt链接到演示:https://godbolt.org/z/xvvx4zT4s

h79rfbju

h79rfbju1#

我通过删除标记的委托构造函数并使用placement new构造函数来修复这个问题。

// ----------------------------------------
  // 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_));
    }
  }
  // ----------------------------------------

相关问题