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

问题描述 投票:0回答:1

我即将完成我的容器,但我要解决的最后一个问题是如何处理复制/移动构造函数并在私有联合成员变量中正确构造正确的成员变量。我希望我的复制和移动构造函数仅根据 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

c++ constructor union copy-constructor move-constructor
1个回答
0
投票

我通过删除标记的委托构造函数并使用放置新构造函数来解决此问题。

  // ----------------------------------------
  // 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_));
    }
  }
  // ----------------------------------------
© www.soinside.com 2019 - 2024. All rights reserved.