Tại sao std :: trao đổi constexpr được đánh dấu trước C ++ 20?


14

Trong C ++ 20, std::swaptrở thành một constexprhàm.

Tôi biết rằng thư viện tiêu chuẩn thực sự bị tụt hậu so với ngôn ngữ trong việc đánh dấu mọi thứ constexpr, nhưng vào năm 2017, <algorithm>đã có khá nhiều điều khó hiểu cũng như một loạt các thứ khác. Tuy nhiên - std::swapkhông phải. Tôi mơ hồ nhớ rằng có một số khiếm khuyết ngôn ngữ lạ ngăn cản việc đánh dấu đó, nhưng tôi quên các chi tiết.

Ai đó có thể giải thích điều này ngắn gọn và rõ ràng?

Động lực: Cần hiểu lý do tại sao có thể đánh dấu một std::swap()chức năng giống như constexprtrong mã C ++ 11 / C ++ 14.

Câu trả lời:


11

Vấn đề ngôn ngữ lạ là CWG 1581 :

Điều 15 [đặc biệt] hoàn toàn rõ ràng rằng các hàm thành viên đặc biệt chỉ được định nghĩa ngầm khi chúng được sử dụng odr. Điều này tạo ra một vấn đề cho các biểu thức không đổi trong bối cảnh không được đánh giá:

struct duration {
  constexpr duration() {}
  constexpr operator int() const { return 0; }
};

// duration d = duration(); // #1
int n = sizeof(short{duration(duration())});

Vấn đề ở đây là chúng tôi không được phép định nghĩa ngầm constexpr duration::duration(duration&&)trong chương trình này, vì vậy biểu thức trong danh sách khởi tạo không phải là biểu thức không đổi (vì nó gọi hàm constexpr chưa được xác định), do đó, trình khởi tạo giằng có chứa một chuyển đổi thu hẹp , vì vậy chương trình không thành hình.

Nếu chúng tôi bỏ dòng số 1, hàm tạo di chuyển được xác định ngầm định và chương trình hợp lệ. Hành động ma quái này ở một khoảng cách là vô cùng đáng tiếc. Triển khai phân kỳ về điểm này.

Bạn có thể đọc phần còn lại của mô tả vấn đề.

Một giải pháp cho vấn đề này đã được thông qua trong P0859 tại Albuquerque vào năm 2017 (sau khi C ++ 17 xuất xưởng). Vấn đề đó là một trình chặn cho cả hai có thể có constexpr std::swap(được giải quyết trong P0879 ) và constexpr std::invoke(được giải quyết trong P1065 , cũng có ví dụ CWG1581), cho cả C ++ 20.


Ví dụ đơn giản nhất để hiểu ở đây, theo tôi, là mã từ báo cáo lỗi LLVM được chỉ ra trong P1065:

template<typename T>
int f(T x)
{
    return x.get();
}

template<typename T>
constexpr int g(T x)
{
    return x.get();
}

int main() {

  // O.K. The body of `f' is not required.
  decltype(f(0)) a;

  // Seems to instantiate the body of `g'
  // and results in an error.
  decltype(g(0)) b;

  return 0;
}

CWG1581 là tất cả về khi các chức năng thành viên constexpr được xác định và độ phân giải đảm bảo rằng chúng chỉ được xác định khi sử dụng. Sau P0859, ở trên được hình thành tốt (loại bint).

std::swapstd::invokecả hai đều phải dựa vào việc kiểm tra các chức năng thành viên (di chuyển xây dựng / chuyển nhượng trong cuộc gọi trước và cuộc gọi điều hành / thay thế cuộc gọi sau), cả hai đều phụ thuộc vào việc giải quyết vấn đề này.


Vậy, tại sao CWG-1581 ngăn chặn / làm cho nó không mong muốn để đánh dấu một chức năng trao đổi là constexpr?
einpoklum

3
@einpoklum trao đổi yêu cầu std::is_move_constructible_v<T> && std::is_move_assignable_v<T>true. Điều đó không thể xảy ra nếu các chức năng thành viên đặc biệt chưa được tạo.
NathanOliver

@NathanOliver: Đã thêm câu này vào câu trả lời của tôi.
einpoklum

5

Nguyên nhân

(do @NathanOliver)

Để cho phép một constexprchức năng hoán đổi, bạn phải kiểm tra - trước khi khởi tạo mẫu cho chức năng này - rằng loại hoán đổi là di chuyển có thể xây dựng và di chuyển được. Thật không may, do lỗi ngôn ngữ chỉ được giải quyết trong C ++ 20, bạn không thể kiểm tra điều đó, vì các hàm thành viên có liên quan có thể chưa được xác định, theo như trình biên dịch có liên quan.

Trình tự thời gian

  • 2016: Antony Polukhin gửi đề xuất P0202 , để đánh dấu tất cả các <algorithm>chức năng như constexpr.
  • Nhóm làm việc cốt lõi của ủy ban tiêu chuẩn thảo luận về khiếm khuyết CWG-1581 . Vấn đề này làm cho nó có vấn đề để có constexpr std::swap()và cũng có constexpr std::invoke()- xem giải thích ở trên.
  • 2017: Antony sửa đổi đề xuất của mình một vài lần để loại trừ std::swapvà một số cấu trúc khác, và điều này được chấp nhận vào C ++ 17.
  • 2017: Nghị quyết về vấn đề CWG-1581 được đệ trình là P0859 và được ủy ban tiêu chuẩn chấp nhận vào năm 2017 (nhưng sau khi C ++ 17 xuất xưởng).
  • Cuối năm 2017: Antony đệ trình một đề xuất bổ sung, P0879 , để thực hiện std::swap()constexpr sau khi giải quyết CWG-1581.
  • 2018: Đề xuất bổ sung được chấp nhận (?) Vào C ++ 20. Như Barry chỉ ra, bản std::invoke()sửa lỗi constexpr cũng vậy .

Trường hợp cụ thể của bạn

Bạn có thể sử dụng constexprhoán đổi nếu bạn không kiểm tra khả năng di chuyển và khả năng chuyển nhượng, mà thay vào đó trực tiếp kiểm tra một số tính năng khác của các loại đảm bảo cụ thể. ví dụ chỉ các loại nguyên thủy và không có lớp hoặc cấu trúc. Hoặc, về mặt lý thuyết, bạn có thể từ bỏ các kiểm tra và xử lý bất kỳ lỗi biên dịch nào bạn có thể gặp phải và với chuyển đổi hành vi không ổn định giữa các trình biên dịch. Trong mọi trường hợp, đừng thay thế std::swap()bằng loại điều đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.