2017-05-31 11 views
34

tôi thấy mã thú vị sau ngày hôm nay:Lẫn lộn bằng dấu phẩy trong biểu ternary

SomeFunction(some_bool_variable ? 12.f, 50.f : 50.f, 12.f) 

Tôi tạo ra một mẫu nhỏ để tạo lại hành vi:

class Vector3f 
{ 
public: 
    Vector3f(float val) 
    { 
     std::cout << "vector constructor: " << val << '\n'; 
    } 
}; 

void SetSize(Vector3f v) 
{ 
    std::cout << "SetSize single param\n"; 
} 

void SetSize(float w, float h, float d=0) 
{ 
    std::cout << "SetSize multi param: " << w << ", " << h << ", " << d << '\n'; 
} 

int main() 
{ 
    SetSize(true ? 12.f, 50.f : 50.f, 12.f); 
    SetSize(false ? 12.f, 50.f : 50.f, 12.f); 
} 

(Live Sample)

Kết quả Tôi nhận được từ việc chạy mã trên là:

clang++ -std=c++14 -O2 -Wall -pedantic -lboost_system -lboost_filesystem -pthread main.cpp && ./a.out 
main.cpp:29:20: warning: expression result unused [-Wunused-value] 
    SetSize(true ? 12.f, 50.f : 50.f, 12.f); 
        ^~~~ 
main.cpp:30:21: warning: expression result unused [-Wunused-value] 
    SetSize(false ? 12.f, 50.f : 50.f, 12.f); 
        ^~~~ 
2 warnings generated. 
SetSize multi param: 50, 12, 0 
SetSize multi param: 50, 12, 0 

Điều tôi mong đợi ở cả hai trường hợp là một thông số duy nhất sẽ được chuyển đến SetSize(float). Tuy nhiên, hai thông số được truyền mà tôi thấy cực kỳ khó hiểu (đặc biệt là vì ternary có ưu tiên hơn dấu phẩy; vì vậy tôi giả định rằng dấu phẩy không định nghĩa các đối số hàm trong trường hợp này). Ví dụ: nếu sử dụng true, số ba phải là 12.f, 50.f. Trong cụm từ này, giá trị bên trái của dấu phẩy được giảm/bỏ qua, vì vậy tôi mong đợi kết quả cuối cùng là:

SetSize(50.f); 

Phần thứ hai của sự nhầm lẫn là liệu chúng tôi sử dụng true hoặc false trong ternary, cùng 2 giá trị được truyền cho hàm. Các trường hợp true nên là h=12, w=50 Tôi nghĩ rằng ...

Tôi thấy trình biên dịch đang cố gắng cảnh báo cho tôi về điều gì đó, nhưng tôi không thể hiểu được điều gì đang xảy ra. Ai đó có thể phân hủy logic này và giải thích kết quả theo từng bước?

+1

Bạn không thể cung cấp danh sách đối số với toán tử bậc ba. Nó chỉ chọn giá trị. – EJP

+6

@EJP: Hãy sử dụng phần trả lời để trả lời câu hỏi. Cảm ơn. –

+0

Ngữ pháp ngôn ngữ cho toán tử điều kiện không cho phép biểu thức toán tử dấu phẩy cho phần cuối cùng (để tránh sự mơ hồ) –

Trả lời

30

Trong khi phần thứ hai của toán tử ternary là khép kín, phần thứ ba thì không. Ngữ pháp là như sau:

có điều kiện thể hiện:

logic hoặc thể hiện

logic hoặc thể hiện? biểu hiện: nhiệm vụ thể hiện

Vì vậy, cuộc gọi chức năng của bạn là một cách hiệu quả này:

SetSize((true ? (12.f, 50.f): 50.f), 12.f) 

Vì vậy, các biểu ternary true ? (12.f, 50.f): 50.f được đánh giá là tham số đầu tiên của hàm. Sau đó, 12.f được chuyển làm giá trị thứ hai. Dấu phẩy trong trường hợp này là không phải là toán tử dấu phẩy nhưng là dấu tách tham số chức năng.

Từ phần 5,18 của C++ standard:

Trong bối cảnh nơi dấu phẩy được đưa ra một ý nghĩa đặc biệt, [ Ví dụ: trong danh sách các đối số cho chức năng (5.2.2) và danh sách khởi tạo (8.5) - ví dụ kết thúc] toán tử dấu phẩy như được mô tả trong khoản 5 chỉ có thể xuất hiện trong dấu ngoặc đơn. [ Ví dụ:

f(a, (t=3, t+2), c); 

có ba đối số, thứ hai trong số đó có giá trị 5. - cuối dụ]

Nếu bạn muốn hai subexpressions cuối cùng được nhóm lại với nhau, bạn cần phải thêm dấu ngoặc đơn:

SetSize(true ? 12.f, 50.f : (50.f, 12.f)); 
SetSize(false ? 12.f, 50.f : (50.f, 12.f)); 

Bây giờ bạn có một nhà điều hành dấu phẩy và các phiên bản đối số duy nhất của SetSize được gọi.

17

Điều này là do C++ does not treat the second comma as a comma operator:

Dấu phẩy trong nhiều danh sách bằng dấu phẩy, chẳng hạn như danh sách chức năng lập luận f(a, b, c) và danh sách initializer int a[] = {1,2,3}, không phải là các nhà điều hành dấu phẩy.

Theo như dấu phẩy đầu tiên có liên quan, C++ không có tùy chọn nào khác ngoài việc coi nó là toán tử dấu phẩy. Nếu không, phân tích cú pháp sẽ không hợp lệ. Một cách đơn giản để xem nó là nghĩ rằng ngay khi trình phân tích cú pháp C++ tìm thấy ? trong ngữ cảnh mà dấu phân cách bằng dấu phẩy được cho phép, nó sẽ tìm kiếm kết quả phù hợp với phần đầu tiên của biểu thức, và sau đó khớp với ít nhất là là cần thiết để hoàn thành biểu thức thứ hai. Dấu phẩy thứ hai sẽ không được coi là toán tử ngay cả khi bạn loại bỏ quá tải hai đối số.

10

Trình biên dịch cảnh báo bạn rằng bạn đang ném đi chính xác 50% số điểm nổi của bạn.

Hãy phân tích nó.

// void SetSize(float w, float h, float d=0) 
SetSize(true ? 12.f, 50.f : 50.f, 12.f); 
//  ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ 

Dưới đây chúng tôi trình bày một biểu thức bằng cách sử dụng có điều kiện khai thác như là đối số đầu tiên, và theo nghĩa đen 12.f như là đối số thứ hai; đối số thứ ba được để mặc định (0).

Có, thực sự.

Nó phân tích như thế này (vì không có cách nào có giá trị khác để phân tích nó):

SetSize((true ? 12.f, 50.f : 50.f), 12.f); 
//  ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ 

Giá trị của đối số thứ hai là đơn giản, vì vậy hãy kiểm tra đầu tiên:

true ? 12.f, 50.f : 50.f 

này có nghĩa là:

  • Nếu đúng, kết quả là 12.f, 50.f
  • Nếu không, kết quả là 50.f

Vâng, đúng là luôn đúng, vì vậy chúng tôi có thể giảm ngay tùy chọn thứ hai.

Và khái niệm 12.f, 50.f sử dụng các nhà điều hành dấu phẩy, mà đánh giá cả hai toán hạng sau đó bàn cặp đi đầu tiên và kết quả trong lần thứ hai, ví dụ: 50.f.

Do đó, toàn bộ điều là thực sự:

SetSize(50.f, 12.f); 

Nếu đây không phải là một số phức tạp và vô nghĩa lập trình "câu đố", đó là một mảnh khá ngu ngốc mã, với một lập trình viên thất học hy vọng có thể "giải nén" các biểu hiện thành nội dung nào đó tương đương với:

SetSize(
    (true ? 12.f : 50.f), 
    (true ? 50.f : 12.f) 
); 

& hellip; đó là vẫn còn mã khủng khiếp và vô dụng, bởi vì sự thật vẫn luôn đúng.

(Rõ ràng, các giá trị khác nhau trong trường hợp đó false được viết thay vào đó, nhưng cùng một logic được áp dụng.)


Trường hợp đúng nên h = 12, w = 50 Tôi d nghĩ ...

Có. Đó là những gì đầu ra bạn đã đăng nói. Rõ ràng hơn khi bạn không tự ý sắp xếp lại các đối số, tức là chúng là w = 50 h = 12.