Làm thế nào để gửi giữa assert () và static_assert (), phụ thuộc nếu trong bối cảnh constexpr?


8

Trong các hàm constexpr của C ++ 11, một câu lệnh thứ hai như assert()là không thể thực hiện được. A static_assert()là tốt, nhưng sẽ không hoạt động nếu chức năng được gọi là chức năng 'bình thường'. Toán tử dấu phẩy có thể đến để giúp wrto. các assert(), nhưng là xấu xí và một số công cụ nhổ cảnh báo về điều đó.

Hãy xem xét 'getter' như vậy hoàn toàn có thể tạo ra bên cạnh khẳng định. Nhưng tôi muốn giữ một số loại xác nhận cho thời gian chạy và biên dịch thời gian, nhưng không thể quá tải tùy thuộc vào bối cảnh 'constexpr'.

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    ASSERT( idx < Size ); // a no-go for constexpr funcs in c++11
    // not possible, even in constexpr calls as being pointed out, but what I would like:
    static_assert( idx < Size, "out-of-bounds" );
    return m_vals[idx];
  }
};

Điều kiện bên lề: C ++ 11, không heap, không có ngoại lệ, không có chi tiết cụ thể về trình biên dịch.

Lưu ý như các nhà bình luận đã chỉ ra (cảm ơn!), static_assertVề lập luận là không thể (nhưng sẽ tốt đẹp). Trình biên dịch đã cho tôi một lỗi khác nhau khi truy cập ngoài giới hạn trong tình huống đó.



" nhưng là xấu xí " Vâng, toán tử dấu phẩy có thể xấu, nhưng nó thực hiện công việc trong C ++ 11. Giải pháp ưa thích sẽ là chuyển sang C ++ 14. :)
Acorn

" Một số công cụ nhổ cảnh báo về nó " Công cụ nào và cảnh báo nào?
Acorn

Toán tử dấu phẩy từng là giải pháp của tôi, nhưng a) được cảnh báo bằng phân tích mã tĩnh như qacpp (hướng dẫn mã hóa) và b) đã dẫn đến một lỗi cú pháp kỳ lạ tại một dự án hạ nguồn (không hiểu, nghi ngờ một macro xác nhận tùy chỉnh) . Vâng, tôi chỉ cố gắng tránh nó bây giờ, nhưng đồng ý rằng nó đã làm công việc.
Borph

2
@ Không có, bạn không thể sử dụng static_assertphụ thuộc vàoidx tất cả. Bạn chỉ có thể chẩn đoán một giá trị sai idxnếu hàm được sử dụng trong ngữ cảnh yêu cầu biểu thức không đổi, bằng cách buộc đánh giá một cấu trúc làm cho nó không phải là biểu thức hằng. Ngoài bối cảnh như vậy, bạn không bao giờ có thể kiểm tra giá trị tại thời gian biên dịch.
quả óc chó

Câu trả lời:


2

Cái gì đó như

void assert_impl() { assert(false); } // Replace body with own implementation

#ifdef NDEBUG // Replace with own conditional
#define my_assert(condition) ((void)0)
#else
#define my_assert(condition) ((condition) ? (void()) : (assert_impl(), void()))
#endif

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement( int idx ) const
  {
    return my_assert(idx < Size), m_vals[idx];
  }
};

Nó sẽ đưa ra lỗi thời gian biên dịch khi lỗi xác nhận nếu được sử dụng trong ngữ cảnh yêu cầu một biểu thức không đổi (vì nó sẽ gọi một constexprhàm không ).

Nếu không, nó sẽ thất bại trong thời gian chạy với một cuộc gọi đến assert(hoặc tương tự của bạn).

Đây là điều tốt nhất mà bạn có thể làm theo như tôi biết. Không có cách nào để sử dụng giá trị của idxđể buộc kiểm tra tại thời gian biên dịch ngoài ngữ cảnh yêu cầu các biểu thức không đổi.

Cú pháp toán tử dấu phẩy không đẹp, nhưng các hàm C ++ 11 constexprrất hạn chế.

Tất nhiên, như bạn đã lưu ý, dù sao đi nữa, hành vi không xác định sẽ được chẩn đoán nếu hàm được sử dụng trong ngữ cảnh yêu cầu biểu thức không đổi.

Nếu bạn biết rằng assert(hoặc tương tự của bạn) không mở rộng sang bất cứ điều gì bị cấm trong biểu thức không đổi nếu điều kiện ước tính làtrue nhưng nếu nó ước tính false, thì bạn có thể sử dụng trực tiếp thay vì my_assertbỏ qua các hướng dẫn mà tôi xây dựng trong mã của tôi.


1
Đưa ra các downvote, ai đó có thể vui lòng giải thích cho tôi nơi câu trả lời của tôi là sai?
quả óc chó

Giải pháp này và @ecatmur tương tự nhau, tôi chỉ có thể chọn một câu trả lời. Của bạn là thẳng về phía trước. Một nhận xét mặc dù: tại sao (void)0trong NDEBUGtrường hợp này và void()trong trường hợp khác? Hay nó thực sự giống nhau?
Borph

@Borph (void)0là một no-op, nó biên dịch thành không có gì (đó là những gì bạn muốn khi NDEBUGđược định nghĩa), trong khi bạn cần các void()toán hạng thứ hai và thứ ba của toán tử điều kiện có cùng loại , void.
Bob__

@Bor Tôi nghĩ (void)0sẽ ổn trong mọi trường hợp. Tôi chỉ thay thế nó trong trường hợp đầu tiên, bởi vì void()cũng có thể được phân tích cú pháp dưới dạng loại hàm không có tham số và không có kiểu trả về tùy thuộc vào ngữ cảnh. Nó không thể được phân tích cú pháp theo cách đó trong các biểu hiện con trong trường hợp thứ hai.
quả óc chó

3

Tốt hơn là một biểu thức dấu phẩy, bạn có thể sử dụng một điều kiện ternary. Toán hạng thứ nhất là vị từ xác nhận của bạn, toán hạng thứ hai là biểu thức thành công của bạn và vì toán hạng thứ ba có thể là bất kỳ biểu thức nào - thậm chí không thể sử dụng được trong ngữ cảnh không đổi C ++ 11 - bạn có thể sử dụng lambda để gọi ASSERTcơ sở thư viện của mình :

#define ASSERT_EXPR(pred, success)    \
    ((pred) ?                         \
     (success) :                      \
     [&]() -> decltype((success))     \
     {                                \
         ASSERT(false && (pred));     \
         struct nxg { nxg() {} } nxg; \
         return (success);            \
     }())

Giải thích về cơ thể của lambda:

  • ASSERT(false && (pred)) là để đảm bảo rằng máy móc khẳng định của bạn được gọi với một biểu thức thích hợp (để xâu chuỗi).
  • struct nxg { nxg() {} } nxglà để đảm bảo an toàn trong tương lai, để đảm bảo rằng nếu bạn biên dịch trong C ++ 17 trở lên với NDEBUGlambda vẫn không phải constexprvà do đó, xác nhận được thi hành trong bối cảnh đánh giá const.
  • return (success)là có vì hai lý do: để đảm bảo rằng các toán hạng thứ hai và thứ ba có cùng loại, và do đó nếu khía cạnh thư viện của bạn NDEBUGcác successbiểu hiện được trả lại bất kể pred. ( predsẽ được đánh giá , nhưng bạn hy vọng rằng các biến vị ngữ khẳng định sẽ rẻ để đánh giá và không có tác dụng phụ.)

Ví dụ sử dụng:

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr int getElement( int idx ) const
  {
    return ASSERT_EXPR(idx < Size, m_vals[idx]);
  }
};

constexpr int I = Array<2>{1, 2}.getElement(1); // OK
constexpr int J = Array<2>{1, 2}.getElement(3); // fails

Ah! @walnut và một biến chưa được khởi tạo có thể được cho phép sau này nếu nó không được truy cập. Tôi sẽ cố gắng tìm một người bảo vệ tốt hơn. Cảm ơn!
ecatmur

đề nghị: s / [&]/ [&] -> decltype((success))để bảo tồn tài liệu tham khảo.
LF

@LF điểm tốt, cảm ơn!
ecatmur

Bạn đúng rằng predđánh giá phải rẻ, nhưng chúng không phải lúc nào cũng vậy. Vì vậy, như một ASSERT_EXPRmacro chung, tôi sẽ không đề xuất. Đôi khi tôi thực hiện các cuộc gọi đắt tiền để khẳng định bản thân (ví dụ: để kiểm tra bất biến).
Borph

1
@Borph Tôi giả sử rằng ngay cả khi bạn bật NDEBUGđể tắt các xác nhận thời gian chạy, bạn vẫn muốn các xác nhận thời gian biên dịch được kiểm tra. Làm cho phần thân của lambda không phải constexprlà một cách để đảm bảo điều này - nhưng nó có chi phí đánh giá và loại bỏ vị từ trong thời gian chạy NDEBUG. Nếu không, bạn có thể định nghĩa macro theo NDEBUGchỉ return (success);.
ecatmur

2

static_assertkhông thể được sử dụng ở đây. Đối số cho constexprhàm không được phép trong biểu thức hằng. Vì thế, không có giải pháp cho vấn đề của bạn dưới các ràng buộc nhất định.

Tuy nhiên, chúng ta có thể giải quyết vấn đề bằng cách uốn cong hai ràng buộc

  1. không sử dụng static_assert(sử dụng các phương pháp khác để tạo chẩn đoán thời gian biên dịch thay thế) và

  2. coi thường toán tử dấu phẩy "là xấu và một số công cụ đưa ra cảnh báo về nó." (Thể hiện sự xấu xí của nó là hậu quả đáng tiếc của các yêu cầu nghiêm ngặt của các constexprchức năng C ++ 11 )

Sau đó, chúng ta có thể sử dụng bình thường assert:

template <int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    return assert(idx < Size), m_vals[idx];
  }
};

Trong ngữ cảnh đánh giá không đổi, điều này sẽ phát ra lỗi trình biên dịch như thế nào error: call to non-'constexpr' function 'void __assert_fail(const char*, const char*, unsigned int, const char*)'.


-2

Đây là những gì làm việc cho tôi đối với hầu hết các trình biên dịch: https://godbolt.org/z/4nT2ub

template<int Size>
struct Array {
  int m_vals[Size];
  constexpr const int& getElement(int idx) const
  {
    assert(idx < Size);
    return m_vals[idx];
  }
};

Bây giờ static_assertđã lỗi thời vì constexprkhông thể chứa hành vi không xác định. Vì vậy, khi bạn phá vỡ trình biên dịch chỉ mục mảng báo cáo lỗi thích hợp. Xem tại đây .

Vấn đề là assert. Đó là một macro mà việc thực hiện không được xác định. Nếu trình biên dịch sử dụng hàm không phải là constexprnó sẽ thất bại, nhưng như bạn có thể thấy 3 trình biên dịch chính không có vấn đề gì với điều đó.


1
Điều này không hoạt động trong C ++ 11 (một trong những yêu cầu của OP). Trình biên dịch có thể đang sử dụng C ++ 14 trở lên theo mặc định.
quả óc chó

với C++11điều này không thành công trên gcc chỉ có godbolt.org/z/DB2zL3
Marek R

Nhìn vào các thông điệp cảnh báo. MSVC hoàn toàn không chấp nhận /std:c++11cờ và Clang cho phép mã mặc dù nó yêu cầu C ++ 14. Add -pedantic-errorsvà Clang sẽ đưa ra các lỗi thích hợp mà trình biên dịch C ++ 11 thuần túy sẽ đưa ra.
quả óc chó

-3

c ++ 11 không thể như vậy .. hoặc là idxhằng hoặc không
sẽ tốt hơn nếu có một chức năng mỗi chức năng
Nó có thể là loại này nếu bị ép buộc trong một chức năng

template<int Size>
struct Array {
    int m_vals[Size];
    constexpr const  int& getElement( int idx ) const       
    {
    if constexpr(is_same_v<decltype(idx), const int>)
        static_assert( idx < Size, "out-of-bounds" ); // a no-go for non-constexpr calls
    else 
        assert( idx < Size ); // a no-go for constexpr funcs in c++11

    return m_vals[idx];
    }
};

int main() {                // E.g. up to next //

        Array<7> M;  
        int i=M.getElement(1);
}                           //

1
Tôi không chắc liệu đây có phải là một câu trả lời cho câu hỏi hay không, nhưng mã của bạn không hợp lệ trong bất kỳ phiên bản C ++ nào. Vấn đề không phải là có hay không idxconst. Nếu đây chỉ là một đề xuất tiêu chuẩn C ++, thì tôi không thấy nó thuộc về phần trả lời như thế nào.
quả óc chó
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.