Toán tử ép kiểu khác nhau được gọi bởi các trình biên dịch khác nhau


80

Hãy xem xét chương trình C ++ ngắn sau:

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Nếu tôi biên dịch nó trên các trình biên dịch khác nhau, tôi sẽ nhận được nhiều kết quả khác nhau. Với Clang 3.4 và GCC 4.4.7, nó sẽ in true, trong khi Visual Studio 2013 in false, có nghĩa là chúng gọi các toán tử truyền khác nhau tại (bool)b. Hành vi nào là đúng theo tiêu chuẩn?

Trong sự hiểu biết của tôi operator bool()không cần chuyển đổi, trong khi operator int()sẽ đòi hỏi một intđể boolchuyển đổi, do trình biên dịch nên chọn cái đầu tiên. Làm constđiều gì đó với điều đó, chuyển đổi const có được coi là "đắt" hơn bởi trình biên dịch?

Nếu tôi loại bỏ const, tất cả các trình biên dịch đều tạo ra falsenhư đầu ra. Mặt khác, nếu tôi kết hợp hai lớp với nhau (cả hai toán tử sẽ ở cùng một lớp) thì cả ba trình biên dịch sẽ tạo truera đầu ra.


3
Theo mình hiểu thì B2 có 2 toán tử: B :: operator bool () và B2 :: operator int (). Cả hai toán tử không phải là toán tử const và có sự khác biệt giữa phiên bản const và không phải const. Bạn có thể thêm phiên bản const của bool vào B và phiên bản const của int vào B2 và xem những thay đổi.
Tanuki

1
constlà chìa khóa ở đây. Cả hai toán tử là const như nhau hoặc không, chúng hoạt động như mong đợi, boolphiên bản "thắng". Với các hằng số khác nhau, phiên bản không hằng số luôn "chiến thắng" trên mọi nền tảng. Với các lớp dẫn xuất và hằng số khác nhau của các toán tử, VS dường như có một lỗi vì nó hoạt động khác với hai trình biên dịch còn lại.
buc

các hàm tạo mặc định khác nhau có thể chịu trách nhiệm cho các hành vi khác nhau không?
ldgorman

16
EDG cũng in true. Nếu GCC, Clang và EDG đều đồng ý và MSVC không đồng ý, điều đó thường có nghĩa là MSVC đã sai.
Jonathan Wakely

1
Lưu ý: nói chung trong C ++, kiểu trả về không tham gia vào việc phân giải quá tải.
Matthieu M.

Câu trả lời:


51

Tiêu chuẩn cho biết:

Một hàm chuyển đổi trong lớp dẫn xuất không ẩn một hàm chuyển đổi trong một lớp cơ sở trừ khi hai hàm chuyển đổi về cùng một kiểu.

§12.3 [class.conv]

Có nghĩa là điều đó operator boolkhông bị ẩn bởi operator int.

Tiêu chuẩn cho biết:

Trong quá trình giải quyết quá tải, đối số đối tượng ngụ ý không thể phân biệt được với các đối số khác.

§13.3.3.1 [over.match.funcs]

"Đối số đối tượng ngụ ý" trong trường hợp này là b, thuộc loại B2 &. operator boolyêu cầu const B2 &, vì vậy trình biên dịch sẽ phải thêm const bđể gọi operator bool. Điều này - tất cả những thứ khác đều bình đẳng - làm cho operator intmột trận đấu tốt hơn.

Tiêu chuẩn tuyên bố rằng a static_cast(mà kiểu truyền kiểu C đang thực hiện trong trường hợp này) có thể chuyển đổi thành một kiểu T(trong trường hợp này int) nếu:

khai báo T t(e);được định dạng tốt, đối với một số biến tạm thời được phát minh t.

§5.2.9 [expr.static.cast]

Do đó, intcó thể được chuyển đổi thành a bool, và a boolcũng có thể được chuyển đổi thành a bool.

Tiêu chuẩn cho biết:

Các hàm chuyển đổi của Svà các lớp cơ sở của nó được xem xét. Những hàm chuyển đổi không rõ ràng không bị ẩn bên trong Svà kiểu lợi nhuận T hoặc kiểu có thể được chuyển đổi thành kiểu Tthông qua chuỗi chuyển đổi tiêu chuẩn là các hàm ứng cử viên.

§13.3.1.5 [over.match.conv]

Vì vậy, tập hợp quá tải bao gồm operator intoperator bool. Tất cả những thứ khác bằng nhau, operator intlà một kết hợp tốt hơn (vì bạn không cần phải thêm hằng số). Do đó operator intnên được lựa chọn.

Lưu ý rằng (có lẽ chống lại trực giác) tiêu chuẩn không xem xét kiểu trả về (tức là kiểu mà các toán tử này chuyển đổi) khi chúng đã được thêm vào tập quá tải (như đã thiết lập ở trên), cung cấp trình tự chuyển đổi cho các đối số của một trong các chúng vượt trội hơn chuỗi chuyển đổi cho các đối số của đối số kia (do là hằng số, là trường hợp trong trường hợp này).

Tiêu chuẩn cho biết:

Với những định nghĩa này, một hàm khả thi F1 được xác định là một hàm tốt hơn một hàm khả thi khác F2 nếu đối với tất cả các đối số i, ICSi (F1) không phải là một chuỗi chuyển đổi kém hơn ICSi (F2), và sau đó

  • đối với một số đối số j, ICSj (F1) là một chuỗi chuyển đổi tốt hơn ICSj (F2) hoặc, nếu không,
  • ngữ cảnh là một lần khởi tạo bằng chuyển đổi do người dùng xác định và chuỗi chuyển đổi tiêu chuẩn từ kiểu trả về là F1 thành kiểu đích (tức là loại thực thể đang được khởi tạo) là chuỗi chuyển đổi tốt hơn chuỗi chuyển đổi tiêu chuẩn từ kiểu trả về của F2 đến kiểu đích.

§13.3.3 [over.match.best]

Trong trường hợp này, chỉ có một đối số ( thistham số ngầm định ). Chuỗi chuyển đổi cho B2 &=> B2 &(để gọi operator int) vượt trội hơn B2 &=> const B2 &(gọi operator bool), và do đó operator intđược chọn từ tập hợp quá tải mà không cần quan tâm đến việc nó thực sự không chuyển đổi trực tiếp thành bool.


2
Bạn có thể nhận N3337, (tôi tin là) bản nháp đầu tiên sau C ++ 11 và chỉ bao gồm những thay đổi rất rất nhỏ ở đây miễn phí. Đối với việc tìm kiếm phần thích hợp của tiêu chuẩn, thường là vấn đề đánh giá các phần của PDF, tìm hiểu mọi thứ đang ở đâu (bằng cách tra cứu / trích dẫn tiêu chuẩn) và tìm kiếm các từ khóa có liên quan bằng CTRL + F.
Robert Allan Hennigan Leahy

1
@ikh Tôi tìm tài liệu chuẩn C hoặc C ++ hiện tại ở đâu? là câu hỏi hay nhất cho điều này và theo như tôi biết bao gồm tất cả các bản nháp có sẵn cho cả C và C ++.
Shafik Yaghmour

2
Đó là những gì tôi đã nói với "chỉ được áp dụng sau khi xác định chuỗi chuyển đổi nào tốt hơn". Tuy nhiên, tôi nghĩ rằng đây là một phần quan trọng của câu trả lời: có (về lý thuyết) xung đột giữa việc chuyển đổi đối số đối tượng ngụ ý và "chuyển đổi kiểu trả về" và nó được giải quyết rõ ràng bằng các bước giải quyết quá tải khác nhau. [over.match.best] /1.4 chỉ ra rõ ràng sự tách biệt của các bước đó và lý do tại sao trình tự chuyển đổi tiêu chuẩn cuối cùng không quan tâm trong trường hợp này.
dyp

2
Tôi đã chỉnh sửa câu trả lời để kết hợp đề xuất của bạn, giải thích thêm về lý do tại sao loại trả về của các toán tử chuyển đổi này không được xem xét khi chọn loại "tốt nhất".
Robert Allan Hennigan Leahy

2
@RobertAllanHenniganLeahy Vì đó là mục đích của câu hỏi, nên nó không xuất hiện ở đâu đó trong câu trả lời, chứ không chỉ là một bình luận?
Barmar

9

Ngắn

Hàm chuyển đổi operator int()được chọn bởi clang operator bool() constbconst không đủ điều kiện, trong khi toán tử chuyển đổi cho bool là.

Lý do ngắn gọn là các chức năng ứng cử viên cho giải quyết tình trạng quá tải (với tham số đối tượng tiềm ẩn tại chỗ), khi chuyển đổi bđể bool

operator bool (B2 const &);
operator int (B2 &);

trong đó cái thứ hai là một trận đấu tốt hơn vì bconst không đủ điều kiện.

Nếu cả hai chức năng có cùng tiêu chuẩn (cả hai consthoặc không), operator boolđược chọn vì nó cung cấp chuyển đổi trực tiếp.

Chuyển đổi qua ký hiệu truyền, được phân tích từng bước

Nếu chúng ta đồng ý rằng ostream Inserter boolean (std :: basic_ostream :: operator << (bool val) theo [ostream.inserters.arithmetic]) được gọi với giá trị mà kết quả từ một sự chuyển đổi của bđể boolchúng ta có thể thâm nhập vào chuyển đổi .

1. Biểu cảm diễn viên

Dàn diễn viên của b to bool

(bool)b

đánh giá

static_cast<bool>(b)

theo C ++ 11, 5.4 / 4 [expr.cast]const_castkhông áp dụng được (không thêm hoặc bớt const ở đây).

Chuyển đổi tĩnh này được phép cho mỗi C ++ 11, 5.2.9 / 4 [expr.static.cast] , nếu bool t(b);đối với một biến được phát minh t được định dạng tốt. Các câu lệnh như vậy được gọi là khởi tạo trực tiếp theo C ++ 11, 8.5 / 15 [dcl.init] .

2. Khởi tạo trực tiếp bool t(b);

Khoản 16 của các trạng thái đoạn tiêu chuẩn ít được đề cập nhất (tôi nhấn mạnh):

Ngữ nghĩa của các bộ khởi tạo như sau. Kiểu đích là kiểu của đối tượng hoặc tham chiếu đang được khởi tạo và kiểu nguồn là kiểu của biểu thức trình khởi tạo.

[...]

[...] nếu kiểu nguồnkiểu lớp (có thể đủ tiêu chuẩn cv) , các hàm chuyển đổi được xem xét.

Các chức năng chuyển đổi áp dụng được liệt kê và chức năng tốt nhất được chọn thông qua giải quyết quá tải.

2.1 Có những chức năng chuyển đổi nào?

Các hàm chuyển đổi có sẵn operator int ()operator bool() constvì như C ++ 11, 12.3 / 5 [class.conv] cho chúng ta biết:

Một hàm chuyển đổi trong lớp dẫn xuất không ẩn một hàm chuyển đổi trong một lớp cơ sở trừ khi hai hàm chuyển đổi về cùng một kiểu.

Trong khi C ++ 11, 13.3.1.5/1 [over.match.conv] cho biết:

Các hàm chuyển đổi của S và các lớp cơ sở của nó được xem xét.

trong đó S là lớp sẽ được chuyển đổi từ.

2.2 Các chức năng chuyển đổi nào được áp dụng?

C ++ 11, 13.3.1.5/1 [over.match.conv] (mỏ nhấn mạnh):

1 [...] Giả sử rằng “cv1 T” là kiểu của đối tượng đang được khởi tạo và “cv S” là kiểu của biểu thức khởi tạo, với S là kiểu lớp, các hàm ứng cử viên được chọn như sau: các chức năng của S và các lớp cơ sở của nó được xem xét. Các hàm chuyển đổi không rõ ràng không bị ẩn trong S và loại năng suất T hoặc một loại có thể được chuyển đổi thành loại T thông qua trình tự chuyển đổi tiêu chuẩn là các hàm ứng viên.

Do đó operator bool () constcó thể áp dụng vì nó không bị ẩn bên trong B2và tạo ra a bool.

Phần được nhấn mạnh trong trích dẫn tiêu chuẩn cuối cùng có liên quan đến chuyển đổi sử dụng operator int ()intlà một loại có thể được chuyển đổi thành bool thông qua trình tự chuyển đổi tiêu chuẩn. Chuyển đổi từ intthành boolthậm chí không phải là một chuỗi mà là một chuyển đổi trực tiếp đơn giản được phép theo C ++ 11, 4,12 / 1 [tr.b.bool]

Giá trị pr của kiểu số học, kiểu liệt kê, con trỏ hoặc con trỏ đến kiểu thành viên có thể được chuyển đổi thành giá trị pr kiểu bool. Giá trị 0, giá trị con trỏ null hoặc giá trị con trỏ thành viên null được chuyển đổi thành false; bất kỳ giá trị nào khác được chuyển đổi thành true.

Điều này có nghĩa là điều đó operator int ()cũng có thể áp dụng được.

2.3 Chọn chức năng chuyển đổi nào?

Việc lựa chọn chức năng chuyển đổi thích hợp được thực hiện thông qua độ phân giải quá tải ( C ++ 11, 13.3.1.5/1 [over.match.conv] ):

Độ phân giải quá tải được sử dụng để chọn chức năng chuyển đổi được gọi.

Có một điều "khó hiểu" đặc biệt khi nói đến giải quyết quá tải cho các hàm thành viên của lớp: tham số đối tượng ngầm định ".

Theo C ++ 11, 13.3.1 [over.match.funcs] ,

[...] cả hàm thành viên tĩnh và không tĩnh đều có một tham số đối tượng ngầm định [...]

trong đó loại tham số này cho các hàm thành viên không tĩnh - theo điều khoản 4 là:

  • “Tham chiếu giá trị đến cv X” cho các hàm được khai báo mà không có bộ định nghĩa ref hoặc với bộ định nghĩa & ref

  • “Tham chiếu giá trị đến cv X” cho các hàm được khai báo với bộ định nghĩa && ref

trong đó X là lớp mà hàm là thành viên và cv là cấp độ cv trên khai báo hàm thành viên.

Điều này có nghĩa là (theo C ++ 11, 13.3.1.5/2 [over.match.conv] ), trong một lần khởi tạo bằng hàm chuyển đổi,

Danh sách đối số [t] he có một đối số, đó là biểu thức khởi tạo. [Lưu ý: Đối số này sẽ được so sánh với tham số đối tượng ngầm định của các hàm chuyển đổi. —Gửi ghi chú]

Các hàm ứng cử viên để giải quyết quá tải là:

operator bool (B2 const &);
operator int (B2 &);

Rõ ràng, operator int ()là một kết quả phù hợp hơn nếu một chuyển đổi được yêu cầu bằng cách sử dụng một đối tượng không cố định của loại B2operator bool ()yêu cầu chuyển đổi đủ điều kiện.

Nếu cả hai hàm chuyển đổi có cùng tiêu chuẩn const, thì việc giải quyết quá tải của các hàm đó sẽ không còn hiệu quả nữa. Trong trường hợp này, xếp hạng chuyển đổi (trình tự) được đưa ra.

3. Tại sao lại được operator bool ()chọn khi cả hai hàm chuyển đổi có cùng tiêu chuẩn const?

Chuyển đổi từ B2sang boollà một chuỗi chuyển đổi do người dùng xác định ( C ++ 11, 13.3.3.1.2 / 1 [over.ics.user] )

Trình tự chuyển đổi do người dùng xác định bao gồm trình tự chuyển đổi tiêu chuẩn ban đầu, tiếp theo là chuyển đổi do người dùng xác định, sau đó là trình tự chuyển đổi tiêu chuẩn thứ hai.

[...] Nếu chuyển đổi do người dùng xác định được chỉ định bởi một hàm chuyển đổi, thì trình tự chuyển đổi tiêu chuẩn ban đầu sẽ chuyển đổi kiểu nguồn thành tham số đối tượng ngầm định của hàm chuyển đổi.

C ++ 11, 13.3.3.2/3 [over.ics.rank]

[...] xác định thứ tự từng phần của chuỗi chuyển đổi ngầm định dựa trên các mối quan hệ trình tự chuyển đổi tốt hơn và chuyển đổi tốt hơn.

[...] Trình tự chuyển đổi do người dùng xác định U1 là trình tự chuyển đổi tốt hơn trình tự chuyển đổi khác do người dùng xác định U2 nếu chúng chứa cùng hàm chuyển đổi do người dùng xác định hoặc phương thức khởi tạo hoặc khởi tạo tổng hợp và trình tự chuyển đổi tiêu chuẩn thứ hai của U1 tốt hơn trình tự chuyển đổi tiêu chuẩn thứ hai của U2.

Việc chuyển đổi tiêu chuẩn thứ hai là trường hợp operator bool()boolđể bool(chuyển đổi danh tính) trong khi việc chuyển đổi tiêu chuẩn thứ hai trong trường hợp operator int ()intđể boolđó là một sự chuyển đổi boolean.

Do đó, trình tự chuyển đổi, bằng cách sử dụng operator bool (), sẽ tốt hơn nếu cả hai hàm chuyển đổi có cùng tiêu chuẩn const.


Trong hầu hết các trường hợp, khá trực quan rằng những gì bạn làm với kết quả không ảnh hưởng đến cách nó được tính toán. Cách chúng tôi tính toán a/bgiống nhau cho dù mã là float f = a/b;hay int f = a/b;. Nhưng đây là một trường hợp có thể hơi ngạc nhiên.
David Schwartz

1

Kiểu bool trong C ++ có hai giá trị - true và false với các giá trị tương ứng 1 và 0. Có thể tránh được sự nhầm lẫn cố hữu nếu bạn thêm toán tử bool trong lớp B2 gọi toán tử bool của lớp cơ sở (B) một cách rõ ràng, sau đó kết quả xuất hiện như sai. Đây là chương trình đã sửa đổi của tôi. Khi đó toán tử bool có nghĩa là toán tử bool chứ không phải toán tử int bằng bất kỳ phương tiện nào.

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
    operator bool() {
        return B::operator bool();
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Trong ví dụ của bạn, (bool) b đang cố gắng gọi toán tử bool cho B2, B2 đã kế thừa toán tử bool và toán tử int, theo quy tắc thống trị, toán tử int được gọi và toán tử bool kế thừa trong B2. Tuy nhiên, bằng cách rõ ràng có một toán tử bool trong chính lớp B2, vấn đề sẽ được giải quyết.


8
Đó là một giải pháp tốt để viết chương trình, nhưng nó không giải đáp chi tiết tại sao điều kỳ lạ này xảy ra.
Ikh

4
true and false with corresponding values 1 and 0Đó là một sự đơn giản hóa quá mức. truechuyển đổi thành số nguyên 1, nhưng điều đó không có nghĩa là nó "có" giá trị 1. Thật vậy, truecó thể 42ở bên dưới.
Lightness Races ở Orbit

Có toán tử bool rõ ràng trong B2 sẽ tránh được sự nhầm lẫn và phụ thuộc trình biên dịch của những gì được gọi trong B2, toán tử int hoặc toán tử bool.
Tiến sĩ Debasish Jana

Không có sự phụ thuộc của trình biên dịch. Tiêu chuẩn yêu cầu rằng 0 chuyển đổi thành false và 1 chuyển đổi thành true.
Puppy

1
@LightnessRacesinOrbit Thêm vào điểm, có lẽ: a boolkhông bao giờ có giá trị 0 hoặc 1; các giá trị duy nhất mà nó có thể nhận là falsetrue(chuyển đổi thành 0 và 1 nếu boolđược chuyển đổi thành kiểu tích phân).
James Kanze

0

Một số câu trả lời trước đây, đã cung cấp rất nhiều thông tin.

Đóng góp của tôi là, "hoạt động ép kiểu" được biên dịch tương tự, đối với "hoạt động quá tải", tôi đề nghị tạo một hàm với một mã định danh duy nhất cho mỗi hoạt động và sau đó, thay thế nó bằng toán tử hoặc ép kiểu được yêu cầu.

#include <iostream>

class B {
public:
    bool ToBool() const {
        return false;
    }
};

class B2 : public B {
public:
    int ToInt() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << b.ToBool() << std::endl;
}

Và sau đó, áp dụng toán tử hoặc ép kiểu.

#include <iostream>

class B {
public:
    operator bool() {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Chỉ 2 xu của tôi.

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.