Perilaku korsleting dari operator &&
dan ||
merupakan alat yang luar biasa untuk programmer.
Tetapi mengapa mereka kehilangan perilaku ini saat kelebihan beban? Saya memahami bahwa operator hanyalah gula sintaksis untuk fungsi tetapi operator untuk bool
memiliki perilaku ini, mengapa harus dibatasi pada jenis tunggal ini? Apakah ada alasan teknis di balik ini?
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)
{true, false, nil}
. Karenanil&& x == nil
bisa korsleting.std::valarray<bool> a, b, c;
, bagaimana menurut Andaa || b || c
akan mengalami korsleting?operator&&
atauoperator||
dan bergantung pada kedua operan yang dievaluasi. Mempertahankan kompatibilitas ke belakang adalah (atau seharusnya) penting saat menambahkan fitur ke bahasa yang sudah ada.Jawaban:
Semua proses desain menghasilkan kompromi antara tujuan yang saling tidak kompatibel. Sayangnya, proses desain untuk
&&
operator yang kelebihan beban di C ++ menghasilkan hasil akhir yang membingungkan: bahwa fitur yang Anda inginkan&&
- perilaku hubung singkatnya - dihilangkan.Detail tentang bagaimana proses desain itu berakhir di tempat malang ini, yang tidak saya ketahui. Namun relevan untuk melihat bagaimana proses desain selanjutnya memperhitungkan hasil yang tidak menyenangkan ini. Di C #,
&&
operator yang kelebihan beban mengalami korsleting. Bagaimana para desainer C # mencapai itu?Salah satu jawaban lain menyarankan "pengangkatan lambda". Itu adalah:
dapat diwujudkan sebagai sesuatu yang secara moral setara dengan:
dimana argumen kedua menggunakan beberapa mekanisme untuk evaluasi malas sehingga ketika dievaluasi, efek samping dan nilai ekspresi yang dihasilkan. Implementasi dari operator yang kelebihan beban hanya akan melakukan evaluasi malas jika diperlukan.
Ini bukanlah yang dilakukan oleh tim desain C #. (Selain: meskipun pengangkatan lambda adalah apa yang saya lakukan ketika tiba waktunya untuk melakukan representasi pohon ekspresi dari
??
operator, yang memerlukan operasi konversi tertentu untuk dilakukan dengan malas. Menjelaskan bahwa secara detail akan menjadi penyimpangan besar. Cukup untuk dikatakan: pengangkatan lambda berfungsi tetapi cukup kelas berat sehingga kami ingin menghindarinya.)Sebaliknya, solusi C # memecah masalah menjadi dua masalah terpisah:
Oleh karena itu, masalah diselesaikan dengan membuatnya ilegal untuk membebani
&&
secara langsung. Sebaliknya, di C # Anda harus membebani dua operator, yang masing-masing menjawab salah satu dari dua pertanyaan tersebut.class C { // Is this thing "false-ish"? If yes, we can skip computing the right // hand size of an && public static bool operator false (C c) { whatever } // If we didn't skip the RHS, how do we combine them? public static C operator & (C left, C right) { whatever } ...
(Selain: sebenarnya, tiga. C # mensyaratkan bahwa jika operator
false
disediakan maka operatortrue
juga harus disediakan, yang menjawab pertanyaan: apakah hal ini "benar-benar?". Biasanya tidak ada alasan untuk menyediakan hanya satu operator tersebut jadi C # membutuhkan keduanya.)Pertimbangkan pernyataan dalam bentuk:
Kompilator menghasilkan kode untuk ini seperti yang Anda kira Anda telah menulis pseudo-C ini #:
C cresult; C tempLeft = cleft; cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Seperti yang Anda lihat, sisi kiri selalu dievaluasi. Jika ditentukan sebagai "false-ish" maka itulah hasilnya. Jika tidak, sisi kanan dievaluasi, dan operator yang ditentukan pengguna yang bersemangat
&
dipanggil.The
||
Operator didefinisikan dalam cara analog, sebagai doa operator yang benar dan bersemangat|
Operator:cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Dengan mendefinisikan semua empat operator -
true
,false
,&
dan|
- C # memungkinkan Anda untuk tidak hanya mengatakancleft && cright
tetapi juga non-hubungan arus pendekcleft & cright
, dan jugaif (cleft) if (cright) ...
, danc ? consequence : alternative
danwhile(c)
, dan sebagainya.Sekarang, saya mengatakan bahwa semua proses desain adalah hasil kompromi. Di sini perancang bahasa C # berhasil mendapatkan hubungan arus pendek
&&
dan||
kanan, tetapi melakukan hal itu memerlukan kelebihan beban empat operator, bukan dua , yang membingungkan beberapa orang. Fitur benar / salah operator adalah salah satu fitur yang paling tidak dipahami dengan baik di C #. Tujuan memiliki bahasa yang masuk akal dan lugas yang akrab bagi pengguna C ++ ditentang oleh keinginan untuk mengalami korsleting dan keinginan untuk tidak mengimplementasikan lambda lifting atau bentuk evaluasi malas lainnya. Saya pikir itu adalah posisi kompromi yang masuk akal, tetapi penting untuk disadari bahwa itu adalah posisi kompromi. Hanya berbeda posisi kompromi daripada desainer C ++ mendarat.Jika subjek desain bahasa untuk operator semacam itu menarik minat Anda, pertimbangkan untuk membaca seri saya tentang mengapa C # tidak mendefinisikan operator ini pada nullable Boolean:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
sumber
your post
itu tidak relevan.His noticing your distinct writing style
tidak relevan.bool
maka Anda dapat menggunakan&&
dan||
tanpa menerapkanoperator true/false
atauoperator &/|
di C # tidak ada masalah. Masalahnya justru muncul dalam situasi di mana tidak ada konversi menjadibool
mungkin , atau di mana seseorang tidak diinginkan.Intinya adalah bahwa (dalam batasan C ++ 98) operan kanan akan diteruskan ke fungsi operator yang kelebihan beban sebagai argumen. Dengan demikian, itu sudah dievaluasi . Tidak ada
operator||()
atauoperator&&()
kode bisa atau tidak bisa melakukan yang akan menghindari ini.Operator asli berbeda, karena ini bukan fungsi, tetapi diimplementasikan pada level bahasa yang lebih rendah.
Fitur bahasa tambahan bisa membuat non-evaluasi operan kanan mungkin secara sintaksis . Namun, mereka tidak peduli karena hanya ada beberapa kasus tertentu di mana ini akan berguna secara semantik . (Sama seperti
? :
, yang tidak tersedia untuk kelebihan beban sama sekali.(Mereka butuh 16 tahun untuk memasukkan lambda ke dalam standar ...)
Adapun penggunaan semantik, pertimbangkan:
Ini bermuara pada:
template< typename T > ClassA.operator&&( T const & objectB )
Pikirkan tentang apa sebenarnya yang ingin Anda lakukan dengan objectB (jenis tidak diketahui) di sini, selain memanggil operator konversi ke
bool
, dan bagaimana Anda akan memasukkannya ke dalam kata-kata untuk definisi bahasa.Dan jika Anda sedang menelepon konversi ke bool, baik ...
melakukan hal yang sama, sekarang bukan? Jadi mengapa membebani di tempat pertama?
sumber
export
.)bool
Operator konversi untuk kedua kelas juga memiliki akses ke semua variabel anggota, dan berfungsi dengan baik dengan operator bawaan. Ada lagi selain konversi-ke-bool tidak masuk akal semantik untuk evaluasi sirkuit pendek! Cobalah untuk mendekati ini dari sudut pandang semantik, bukan dari sudut pandang sintaksis: Apa yang ingin Anda capai, bukan bagaimana Anda akan melakukannya.&
dan&&
bukan operator yang sama. Terima kasih telah membantu saya menyadarinya.if (x != NULL && x->foo)
membutuhkan korsleting, bukan untuk kecepatan, tetapi untuk keamanan.Sebuah fitur harus dipikirkan, dirancang, diimplementasikan, didokumentasikan dan dikirim.
Sekarang kita memikirkannya, mari kita lihat mengapa sekarang mungkin mudah (dan sulit untuk dilakukan kemudian). Juga perlu diingat bahwa hanya ada sejumlah sumber daya, jadi menambahkannya mungkin akan memotong sesuatu yang lain (Apa yang ingin Anda tinggalkan?).
Secara teori, semua operator dapat mengizinkan perilaku short-circuiting dengan hanya satu fitur bahasa tambahan "minor" , mulai dari C ++ 11 (ketika lambda diperkenalkan, 32 tahun setelah "C dengan kelas" dimulai pada tahun 1979, 16 setelah c ++ 98):
C ++ hanya memerlukan cara untuk menganotasi argumen sebagai lazy -aluation - a hidden-lambda - untuk menghindari evaluasi hingga diperlukan dan diizinkan (prasyarat terpenuhi).
Seperti apa fitur teoritis itu (Ingat bahwa fitur baru harus dapat digunakan secara luas)?
Anotasi
lazy
, yang diterapkan ke fungsi-argumen membuat fungsi template mengharapkan functor, dan membuat kompilator mengemas ekspresi menjadi functor:A operator&&(B b, __lazy C c) {return c;} // And be called like exp_b && exp_c; // or operator&&(exp_b, exp_c);
Ini akan terlihat di bawah sampul seperti:
template<class Func> A operator&&(B b, Func& f) {auto&& c = f(); return c;} // With `f` restricted to no-argument functors returning a `C`. // And the call: operator&&(exp_b, [&]{return exp_c;});
Perhatikan secara khusus bahwa lambda tetap tersembunyi, dan akan dipanggil paling banyak sekali.
Seharusnya tidak ada penurunan kinerja karena hal ini, selain dari berkurangnya peluang eliminasi subekspresi umum.
Selain kompleksitas implementasi dan kompleksitas konseptual (setiap fitur meningkatkan keduanya, kecuali jika cukup memudahkan kompleksitas tersebut untuk beberapa fitur lain), mari kita lihat pertimbangan penting lainnya: Kompatibilitas mundur.
Meskipun fitur bahasa ini tidak akan merusak kode apa pun, fitur ini akan secara halus mengubah API apa pun yang memanfaatkannya, yang berarti penggunaan apa pun di pustaka yang ada akan menjadi perubahan pemutusan yang diam-diam.
BTW: Fitur ini, meskipun lebih mudah digunakan, lebih kuat daripada solusi pemisahan C #
&&
dan||
menjadi dua fungsi masing-masing untuk definisi terpisah.sumber
&&
mengambil satu argumen tipe "pointer ke fungsi mengembalikan T" dan aturan konversi tambahan yang memungkinkan ekspresi argumen tipe T secara implisit diubah menjadi ekspresi lambda. Perhatikan bahwa ini bukan konversi biasa, karena harus dilakukan pada tingkat sintaksis: mengubah pada waktu proses nilai tipe T menjadi fungsi tidak akan berguna karena evaluasi sudah dilakukan.Dengan rasionalisasi retrospektif, terutama karena
dalam rangka untuk menjamin hubungan pendek (tanpa memperkenalkan sintaks baru) operator harus dibatasi
hasilargumen pertama aktual dapat diubah menjadibool
, dankorsleting dapat dengan mudah diekspresikan dengan cara lain, bila diperlukan.
Misalnya, jika kelas
T
memiliki asosiasi&&
dan||
operator, maka ekspresiauto x = a && b || c;
di mana
a
,b
danc
adalah ekspresi tipeT
, dapat diekspresikan dengan hubung singkat sebagaiauto&& and_arg = a; auto&& and_result = (and_arg? and_arg && b : and_arg); auto x = (and_result? and_result : and_result || c);
atau mungkin lebih jelas
auto x = [&]() -> T_op_result { auto&& and_arg = a; auto&& and_result = (and_arg? and_arg && b : and_arg); if( and_result ) { return and_result; } else { return and_result || b; } }();
Redundansi yang terlihat mempertahankan efek samping dari panggilan operator.
Sementara lambda rewrite lebih bertele-tele, enkapsulasi yang lebih baik memungkinkan seseorang untuk mendefinisikan operator tersebut.
Saya tidak sepenuhnya yakin dengan kesesuaian standar dari semua berikut ini (masih sedikit influensa), tetapi dikompilasi dengan rapi dengan Visual C ++ 12.0 (2013) dan MinGW g ++ 4.8.2:
#include <iostream> using namespace std; void say( char const* s ) { cout << s; } struct S { using Op_result = S; bool value; auto is_true() const -> bool { say( "!! " ); return value; } friend auto operator&&( S const a, S const b ) -> S { say( "&& " ); return a.value? b : a; } friend auto operator||( S const a, S const b ) -> S { say( "|| " ); return a.value? a : b; } friend auto operator<<( ostream& stream, S const o ) -> ostream& { return stream << o.value; } }; template< class T > auto is_true( T const& x ) -> bool { return !!x; } template<> auto is_true( S const& x ) -> bool { return x.is_true(); } #define SHORTED_AND( a, b ) \ [&]() \ { \ auto&& and_arg = (a); \ return (is_true( and_arg )? and_arg && (b) : and_arg); \ }() #define SHORTED_OR( a, b ) \ [&]() \ { \ auto&& or_arg = (a); \ return (is_true( or_arg )? or_arg : or_arg || (b)); \ }() auto main() -> int { cout << boolalpha; for( int a = 0; a <= 1; ++a ) { for( int b = 0; b <= 1; ++b ) { for( int c = 0; c <= 1; ++c ) { S oa{!!a}, ob{!!b}, oc{!!c}; cout << a << b << c << " -> "; auto x = SHORTED_OR( SHORTED_AND( oa, ob ), oc ); cout << x << endl; } } } }
Keluaran:
Di sini setiap
!!
bang-bang menunjukkan konversi kebool
, yaitu pemeriksaan nilai argumen.Karena kompiler dapat dengan mudah melakukan hal yang sama, dan sebagai tambahan mengoptimalkannya, ini adalah implementasi yang mungkin didemonstrasikan dan setiap klaim ketidakmungkinan harus dimasukkan ke dalam kategori yang sama dengan klaim ketidakmungkinan pada umumnya, yaitu, umumnya omong kosong.
sumber
&&
- perlu ada garis tambahan sepertiif (!a) { return some_false_ish_T(); }
- dan ke poin pertama Anda: korsleting adalah tentang parameter yang dapat diubah menjadi bool, bukan hasilnya.bool
diperlukan untuk melakukan hubungan arus pendek.||
tetapi tidak&&
. Komentar lain ditujukan pada imo " harus dibatasi pada hasil yang dapat diubah menjadi bool" di poin pertama Anda - harus dibaca "terbatas pada parameter yang dapat diubah menjadi bool".bool
untuk memeriksa korsleting operator lebih lanjut dalam ekspresi tersebut. Seperti, hasil daria && b
harus dikonversi kebool
untuk memeriksa korsleting dari logika OR dia && b || c
.tl; dr : tidak sepadan dengan usaha, karena permintaan yang sangat rendah (siapa yang akan menggunakan fitur tersebut?) dibandingkan dengan biaya yang agak tinggi (diperlukan sintaks khusus).
Hal pertama yang terlintas dalam pikiran adalah bahwa operator overloading hanyalah cara yang bagus untuk menulis fungsi, sedangkan versi boolean dari operator
||
dan&&
merupakan barang buitlin. Itu berarti bahwa compiler memiliki kebebasan untuk melakukan short-circuit, sedangkan ekspresix = y && z
dengan nonbooleany
danz
harus mengarah ke pemanggilan fungsi sepertiX operator&& (Y, Z)
. Ini berarti ituy && z
hanyalah cara yang bagus untuk menulisoperator&&(y,z)
yang hanya merupakan panggilan dari fungsi bernama aneh di mana kedua parameter harus dievaluasi sebelum memanggil fungsi (termasuk apa pun yang akan dianggap sesuai dengan hubungan arus pendek).Namun, orang dapat berargumen bahwa itu harus memungkinkan untuk membuat terjemahan
&&
operator agak lebih canggih, seperti untuknew
operator yang diterjemahkan ke dalam memanggil fungsi yangoperator new
diikuti oleh panggilan konstruktor.Secara teknis ini tidak akan menjadi masalah, seseorang harus mendefinisikan sintaks bahasa khusus untuk prasyarat yang memungkinkan terjadinya hubungan arus pendek. Namun, penggunaan hubung singkat akan dibatasi untuk kasus-kasus di mana
Y
dapat diterimaX
, atau harus ada info tambahan tentang bagaimana sebenarnya melakukan korsleting (yaitu menghitung hasil hanya dari parameter pertama). Hasilnya akan terlihat seperti ini:X operator&&(Y const& y, Z const& z) { if (shortcircuitCondition(y)) return shortcircuitEvaluation(y); <"Syntax for an evaluation-Point for z here"> return actualImplementation(y,z); }
Jarang ada yang ingin membebani
operator||
danoperator&&
, karena jarang ada kasus di mana menulisa && b
sebenarnya intuitif dalam konteks nonboolean. Satu-satunya pengecualian yang saya ketahui adalah template ekspresi, misalnya untuk DSL yang disematkan. Dan hanya segelintir dari sedikit kasus itu yang akan mendapat manfaat dari evaluasi korsleting. Templat ekspresi biasanya tidak, karena digunakan untuk membentuk pohon ekspresi yang dievaluasi nanti, jadi Anda selalu membutuhkan kedua sisi ekspresi.Singkatnya: baik penulis kompiler maupun penulis standar merasa perlu untuk melompati rintangan dan mendefinisikan serta mengimplementasikan sintaks tambahan yang rumit, hanya karena satu dari sejuta mungkin mendapatkan gagasan bahwa alangkah baiknya memiliki arus pendek pada yang ditentukan pengguna
operator&&
danoperator||
- hanya untuk sampai pada kesimpulan bahwa itu tidak kurang dari usaha menulis logika per tangan.sumber
lazy
yang mengubah ekspresi yang diberikan sebagai argumen secara implisit menjadi fungsi anonim. Ini memberi fungsi yang dipanggil pilihan untuk memanggil argumen itu, atau tidak. Jadi jika bahasa sudah memiliki lambda, sintaks tambahan yang dibutuhkan sangat kecil. "Pseudocode": X dan (A a, lazy B b) {if (cond (a)) {return short (a); } lain {aktual (a, b ()); }}std::function<B()>
, yang akan menimbulkan overhead tertentu. Atau jika Anda mau sebaris itu membuatnyatemplate <class F> X and(A a, F&& f){ ... actual(a,F()) ...}
. Dan mungkin membebani denganB
parameter "normal" , sehingga pemanggil dapat memutuskan versi mana yang akan dipilih. Thelazy
sintaks mungkin lebih nyaman namun memiliki tradeoff kinerja tertentu.std::function
versuslazy
adalah bahwa yang pertama dapat dievaluasi beberapa kali. Parameter malasfoo
yang digunakan sebagaifoo+foo
masih hanya dievaluasi sekali.X
dapat dihitung berdasarkanY
sendiri. Sangat berbeda.std::ostream& operator||(char* a, lazy char*b) {if (a) return std::cout<<a;return std::cout<<b;}
. Kecuali jika Anda menggunakan penggunaan "konversi" yang sangat biasa.operator&&
dengan tangan. Pertanyaannya bukanlah apakah itu mungkin, tetapi mengapa tidak ada jalan pintas yang nyaman.Lambdas bukanlah satu-satunya cara untuk memperkenalkan kemalasan. Evaluasi malas relatif langsung menggunakan Template Ekspresi di C ++. Tidak perlu kata kunci
lazy
dan dapat diterapkan di C ++ 98. Pohon ekspresi sudah disebutkan di atas. Templat ekspresi adalah pohon ekspresi orang yang buruk (tapi pintar). Triknya adalah dengan mengubah ekspresi menjadi pohon instanceExpr
template yang bertingkat secara rekursif . Pohon dievaluasi secara terpisah setelah konstruksi.Kode berikut mengimplementasikan hubung singkat
&&
dan||
operator untuk kelasS
selama kode tersebut menyediakanlogical_and
danlogical_or
fungsi gratis serta dapat diubah menjadibool
. Kode ada di C ++ 14 tetapi idenya dapat diterapkan di C ++ 98 juga. Lihat contoh langsung .#include <iostream> struct S { bool val; explicit S(int i) : val(i) {} explicit S(bool b) : val(b) {} template <class Expr> S (const Expr & expr) : val(evaluate(expr).val) { } template <class Expr> S & operator = (const Expr & expr) { val = evaluate(expr).val; return *this; } explicit operator bool () const { return val; } }; S logical_and (const S & lhs, const S & rhs) { std::cout << "&& "; return S{lhs.val && rhs.val}; } S logical_or (const S & lhs, const S & rhs) { std::cout << "|| "; return S{lhs.val || rhs.val}; } const S & evaluate(const S &s) { return s; } template <class Expr> S evaluate(const Expr & expr) { return expr.eval(); } struct And { template <class LExpr, class RExpr> S operator ()(const LExpr & l, const RExpr & r) const { const S & temp = evaluate(l); return temp? logical_and(temp, evaluate(r)) : temp; } }; struct Or { template <class LExpr, class RExpr> S operator ()(const LExpr & l, const RExpr & r) const { const S & temp = evaluate(l); return temp? temp : logical_or(temp, evaluate(r)); } }; template <class Op, class LExpr, class RExpr> struct Expr { Op op; const LExpr &lhs; const RExpr &rhs; Expr(const LExpr& l, const RExpr & r) : lhs(l), rhs(r) {} S eval() const { return op(lhs, rhs); } }; template <class LExpr> auto operator && (const LExpr & lhs, const S & rhs) { return Expr<And, LExpr, S> (lhs, rhs); } template <class LExpr, class Op, class L, class R> auto operator && (const LExpr & lhs, const Expr<Op,L,R> & rhs) { return Expr<And, LExpr, Expr<Op,L,R>> (lhs, rhs); } template <class LExpr> auto operator || (const LExpr & lhs, const S & rhs) { return Expr<Or, LExpr, S> (lhs, rhs); } template <class LExpr, class Op, class L, class R> auto operator || (const LExpr & lhs, const Expr<Op,L,R> & rhs) { return Expr<Or, LExpr, Expr<Op,L,R>> (lhs, rhs); } std::ostream & operator << (std::ostream & o, const S & s) { o << s.val; return o; } S and_result(S s1, S s2, S s3) { return s1 && s2 && s3; } S or_result(S s1, S s2, S s3) { return s1 || s2 || s3; } int main(void) { for(int i=0; i<= 1; ++i) for(int j=0; j<= 1; ++j) for(int k=0; k<= 1; ++k) std::cout << and_result(S{i}, S{j}, S{k}) << std::endl; for(int i=0; i<= 1; ++i) for(int j=0; j<= 1; ++j) for(int k=0; k<= 1; ++k) std::cout << or_result(S{i}, S{j}, S{k}) << std::endl; return 0; }
sumber
Hubung singkat operator logika diperbolehkan karena ini merupakan "pengoptimalan" dalam evaluasi tabel kebenaran yang terkait. Ini adalah fungsi dari logika itu sendiri, dan logika ini didefinisikan.
Operator logika kustom yang kelebihan beban tidak diwajibkan untuk mengikuti logika tabel kebenaran ini.
Oleh karena itu, seluruh fungsi perlu dievaluasi seperti biasa. Compiler harus memperlakukannya sebagai operator (atau fungsi) yang kelebihan beban normal dan masih dapat menerapkan pengoptimalan seperti pada fungsi lainnya.
Orang membebani operator logika karena berbagai alasan. Sebagai contoh; mereka mungkin memiliki arti khusus dalam domain tertentu yang bukan logika "normal" yang biasa digunakan orang.
sumber
Korsleting ini karena tabel kebenaran "dan" dan "atau". Bagaimana Anda tahu operasi apa yang akan didefinisikan pengguna dan bagaimana Anda tahu bahwa Anda tidak perlu mengevaluasi operator kedua?
sumber
: (<condition>)
setelah deklarasi operator untuk menentukan kondisi dimana argumen kedua tidak dievaluasi?Saya hanya ingin menjawab bagian yang satu ini. Alasannya adalah bahwa ekspresi
&&
dan||
bawaan tidak diimplementasikan dengan fungsi seperti operator yang kelebihan beban.Memiliki logika hubung singkat bawaan untuk pemahaman kompiler tentang ekspresi tertentu itu mudah. Ini sama seperti aliran kontrol bawaan lainnya.
Tetapi kelebihan beban operator diimplementasikan dengan fungsi, yang memiliki aturan tertentu, salah satunya adalah bahwa semua ekspresi yang digunakan sebagai argumen dievaluasi sebelum fungsi dipanggil. Aturan yang jelas berbeda dapat didefinisikan, tapi itu pekerjaan yang lebih besar.
sumber
&&
,||
dan,
harus diperbolehkan? Fakta bahwa C ++ tidak memiliki mekanisme untuk memungkinkan kelebihan beban berperilaku seperti apa pun selain panggilan fungsi menjelaskan mengapa kelebihan beban fungsi tersebut tidak dapat melakukan hal lain, tetapi tidak menjelaskan mengapa operator tersebut dapat kelebihan beban sejak awal. Saya menduga alasan sebenarnya adalah karena mereka dimasukkan ke dalam daftar operator tanpa banyak berpikir.