गैर-बुलियन वापसी मूल्य के साथ समानता की तुलना में ओवरलोडिंग होने पर सी ++ 20 में परिवर्तन या क्लैंग-ट्रंक / जीसीसी-ट्रंक में प्रतिगमन?


11

निम्नलिखित कोड c ++ 17 मोड में क्लैंग-ट्रंक के साथ ठीक संकलित करता है लेकिन c ++ 2a (आगामी c ++ 20) मोड में टूट जाता है:

// Meta struct describing the result of a comparison
struct Meta {};

struct Foo {
    Meta operator==(const Foo&) {return Meta{};}
    Meta operator!=(const Foo&) {return Meta{};}
};

int main()
{
    Meta res = (Foo{} != Foo{});
}

यह gcc-trunk या clang-9.0.0 के साथ भी ठीक संकलित करता है: https://godbolt.org/z/8GG8478

क्लच-ट्रंक के साथ त्रुटि और -std=c++2a:

<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
    Meta res = (f != g);
                ~ ^  ~
<source>:6:10: note: candidate function
    Meta operator!=(const Foo&) {return Meta{};}
         ^
<source>:5:10: note: candidate function
    Meta operator==(const Foo&) {return Meta{};}
         ^
<source>:5:10: note: candidate function (with reversed parameter order)

मैं समझता हूं कि C ++ 20 केवल ओवरलोड करने के लिए संभव बना देगा operator==और कंपाइलर स्वचालित रूप operator!=से परिणाम को नकार कर उत्पन्न करेगा operator==। जहां तक ​​मैं समझता हूं, यह केवल तभी तक काम करता है जब तक रिटर्न प्रकार है bool

समस्या के स्रोत Eigen में हम ऑपरेटरों का एक सेट घोषणा करते हैं कि है ==, !=, <, ... के बीच Arrayवस्तुओं या Arrayऔर scalars, जो वापसी (की अभिव्यक्ति) की एक सरणी bool(जो तब तत्व के लिहाज से पहुँचा जा सकता है, या अन्यथा उपयोग किया जाता है )। उदाहरण के लिए,

#include <Eigen/Core>
int main()
{
  Eigen::ArrayXd a(10);
  a.setRandom();
  return (a != 0.0).any();
}

मेरे ऊपर के उदाहरण के विपरीत यह gcc-trunk के साथ भी विफल होता है: https://godbolt.org/z/RWktKs । मैं इसे अभी तक एक गैर-ईजेन उदाहरण में कम करने में कामयाब नहीं हुआ हूं, जो कि क्लैंग-ट्रंक और जीसीसी-ट्रंक दोनों में विफल रहता है (शीर्ष पर उदाहरण काफी सरल है)।

संबंधित मुद्दे की रिपोर्ट: https://gitlab.com/libeigen/eigen/issues/1833

मेरा वास्तविक प्रश्न: क्या यह वास्तव में C ++ 20 में एक ब्रेकिंग परिवर्तन है (और मेटा-ऑब्जेक्ट्स को वापस करने के लिए तुलना ऑपरेटरों को अधिभारित करने की संभावना है), या क्या यह संभवतः क्लैंग / जीसीसी में प्रतिगमन है?


जवाबों:


5

Eigen समस्या निम्न को कम करने के लिए प्रकट होती है:

using Scalar = double;

template<class Derived>
struct Base {
    friend inline int operator==(const Scalar&, const Derived&) { return 1; }
    int operator!=(const Scalar&) const;
};

struct X : Base<X> {};

int main() {
    X{} != 0.0;
}

अभिव्यक्ति के लिए दो उम्मीदवार हैं

  1. से लिखित उम्मीदवार operator==(const Scalar&, const Derived&)
  2. Base<X>::operator!=(const Scalar&) const

प्रति [over.match.funcs] / 4 , के रूप में operator!=के दायरे में आयात नहीं किया गया था Xएक से का उपयोग कर-घोषणा , # 2 के लिए अंतर्निहित वस्तु पैरामीटर के प्रकार है const Base<X>&। परिणामस्वरूप, # 1 में उस तर्क के लिए एक बेहतर निहित रूपांतरण अनुक्रम है (सटीक मिलान, व्युत्पन्न-से-आधार रूपांतरण के बजाय)। # 1 का चयन करना तब प्रोग्राम को बीमार बनाता है।

संभावित सुधार:

  • जोड़े using Base::operator!=;को Derived, या
  • operator==एक के const Base&बजाय लेने के लिए बदलें const Derived&

क्या कोई कारण है कि वास्तविक कोड boolउनके से वापस नहीं आ सकता है operator==? क्योंकि ऐसा लगता है कि नए नियमों के तहत कोड केवल बीमार है।
निकोल बोलस

4
वास्तविक कोड एक शामिल है operator==(Array, Scalar)कि तत्व के लिहाज से तुलना करता है और एक वापसी Arrayकी bool। आप boolसब कुछ तोड़े बिना उसे नहीं बदल सकते ।
टीसी

2
यह मानक में एक दोष जैसा लगता है। पुनर्लेखन के नियम operator==मौजूदा कोड को प्रभावित करने वाले नहीं थे, फिर भी वे इस मामले में करते हैं, क्योंकि boolरिटर्न वैल्यू के लिए चेक पुनर्लेखन के लिए उम्मीदवारों का चयन करने का हिस्सा नहीं है।
निकोल बोलस

2
@ न्यूकोलोलस: सामान्य सिद्धांत का पालन किया जा रहा है कि चेक इस बात के लिए है कि क्या आप कुछ कर सकते हैं ( उदाहरण के लिए, ऑपरेटर का आह्वान करें), न कि क्या आप को , कार्यान्वयन परिवर्तनों से बचने के लिए चुपचाप अन्य कोड की व्याख्या को प्रभावित करना चाहिए । यह पता चला है कि फिर से लिखा तुलना बहुत सारी चीजों को तोड़ती है, लेकिन ज्यादातर चीजें जो पहले से ही संदिग्ध थीं और ठीक करना आसान है। तो, बेहतर या बदतर के लिए, इन नियमों को वैसे भी अपनाया गया था।
डेविस हेरिंग

वाह, बहुत बहुत धन्यवाद, मुझे लगता है कि आपका समाधान हमारे मुद्दे को हल करेगा (मेरे पास समय पर उचित प्रयास के साथ gcc / clang ट्रंक स्थापित करने का समय नहीं है, इसलिए मैं अभी जांच करूंगा कि क्या यह नवीनतम स्थिर संकलक संस्करणों तक कुछ भी तोड़ता है )।
chtz

11

हां, C ++ 20 में कोड वास्तव में टूट जाता है।

Foo{} != Foo{}C ++ 20 में अभिव्यक्ति के तीन उम्मीदवार हैं (जबकि C ++ 17 में केवल एक ही था):

Meta operator!=(Foo& /*this*/, const Foo&); // #1
Meta operator==(Foo& /*this*/, const Foo&); // #2
Meta operator==(const Foo&, Foo& /*this*/); // #3 - which is #2 reversed

यह नए लिखित उम्मीदवार नियमों से आता है [over.match.oper] / 3.4 में । वे सभी उम्मीदवार व्यवहार्य हैं, क्योंकि हमारे Fooतर्क नहीं हैं const। सबसे अच्छा व्यवहार्य उम्मीदवार खोजने के लिए, हमें अपने टाईब्रेकर से गुजरना होगा।

सर्वश्रेष्ठ व्यवहार्य समारोह के लिए प्रासंगिक नियम हैं, [over.match.best] / 2 से :

इन परिभाषाओं को देखते हुए, एक व्यवहार्य फ़ंक्शन F1को किसी अन्य व्यवहार्य फ़ंक्शन की तुलना में बेहतर फ़ंक्शन के रूप में परिभाषित किया जाता है F2यदि सभी तर्कों के लिए i, तब से भी बदतर रूपांतरण क्रम नहीं है , और फिर ICSi(F1)ICSi(F2)

  • [... इस उदाहरण के लिए बहुत सारे अप्रासंगिक मामले ...] या, यदि ऐसा नहीं है, तो
  • F2 एक लिखित उम्मीदवार ([over.match.oper]) है और F1 नहीं है
  • एफ 1 और एफ 2 को फिर से लिखा गया उम्मीदवार है, और एफ 2 एक संश्लेषित उम्मीदवार है जो मापदंडों के उलट क्रम के साथ है और एफ 1 नहीं है

#2और #3फिर से लिखे गए उम्मीदवार हैं, और #3मापदंडों के क्रम को उलट दिया है, जबकि #1फिर से नहीं लिखा गया है। लेकिन उस टाईब्रेकर को प्राप्त करने के लिए, हमें पहले उस प्रारंभिक स्थिति से गुजरना होगा: सभी तर्कों के लिए रूपांतरण क्रम खराब नहीं हैं।

#1से बेहतर है #2क्योंकि सभी रूपांतरण अनुक्रम समान हैं (तुच्छ रूप से, क्योंकि फ़ंक्शन पैरामीटर समान हैं) और #2फिर से लिखे गए उम्मीदवार हैं जबकि #1ऐसा नहीं है।

लेकिन ... दोनों जोड़े #1/ #3और #2/ #3 उस पहली शर्त पर अटक जाते हैं। दोनों मामलों में, पहले पैरामीटर के लिए बेहतर रूपांतरण अनुक्रम होता है #1/ #2जबकि दूसरे पैरामीटर में बेहतर रूपांतरण अनुक्रम होता है #3(उस पैरामीटर के constलिए अतिरिक्त constयोग्यता से गुजरना पड़ता है , इसलिए इसका रूपांतरण रूपांतरण क्रम बेहतर होता है)। यह constफ्लिप-फ्लॉप हमें किसी एक को पसंद करने में सक्षम नहीं होने का कारण बनता है।

नतीजतन, संपूर्ण अधिभार संकल्प अस्पष्ट है।

जहां तक ​​मैं समझता हूं, यह केवल तभी तक काम करता है जब तक रिटर्न प्रकार है bool

यह सही नहीं है। हम बिना शर्त लिखित और प्रत्याशित प्रत्याशियों पर विचार करते हैं। हमारे पास जो नियम है, [over.match.oper] / 9 से :

यदि एक operator==ऑपरेटर के लिए अधिभार संकल्प द्वारा एक फिर से लिखा गया उम्मीदवार चुना जाता है @, तो उसका रिटर्न प्रकार cv होगा bool

यही है, हम अभी भी इन उम्मीदवारों पर विचार करते हैं। लेकिन अगर सबसे अच्छा व्यवहार्य उम्मीदवार एक operator==रिटर्न है, तो कहते हैं, Meta- परिणाम मूल रूप से वैसा ही है जैसे कि उम्मीदवार हटा दिया गया था।

हम ऐसी स्थिति में नहीं रहना चाहते थे जहां ओवरलोड रिज़ॉल्यूशन को रिटर्न प्रकार पर विचार करना होगा। और किसी भी मामले में, यह तथ्य कि यहां कोड रिटर्न Metaहै, सारहीन है - अगर यह वापस आ जाता है तो समस्या भी मौजूद होगी bool


शुक्र है, यहाँ तय आसान है:

struct Foo {
    Meta operator==(const Foo&) const;
    Meta operator!=(const Foo&) const;
    //                         ^^^^^^
};

एक बार जब आप दोनों तुलना ऑपरेटर बनाते हैं const, तो अधिक अस्पष्टता नहीं होती है। सभी पैरामीटर समान हैं, इसलिए सभी रूपांतरण अनुक्रम तुच्छ रूप से समान हैं। #1अब #3दोबारा नहीं लिखकर हराएंगे और #2अब #3उलट नहीं होने से हराएंगे - जो #1सबसे अच्छा व्यवहार्य उम्मीदवार बनाता है। समान परिणाम जो हमारे पास C ++ 17 में था, वहां पहुंचने के लिए बस कुछ और कदम।


" हम एक ऐसी स्थिति में नहीं रहना चाहते थे जहां ओवरलोड रिज़ॉल्यूशन को रिटर्न प्रकार पर विचार करना होगा। " बस स्पष्ट होना चाहिए, जबकि ओवरलोड रिज़ॉल्यूशन स्वयं रिटर्न प्रकार पर विचार नहीं करता है, बाद में फिर से लिखना संचालन करता है । यदि ओवरलोड रिज़ॉल्यूशन फिर से लिखा जाएगा ==और चयनित फ़ंक्शन का रिटर्न प्रकार नहीं है, तो एक कोड बीमार है bool। लेकिन यह culling अधिभार संकल्प के दौरान ही नहीं होता है।
निकोल बोलस

अगर रिटर्न टाइप कुछ ऐसा है जो ऑपरेटर का समर्थन नहीं करता है तो वास्तव में केवल बीमार बन गया है! ...
क्रिस डॉड

1
@ChrisDodd नहीं, यह बिल्कुल होना चाहिए cv bool(और इस परिवर्तन से पहले, आवश्यकता प्रासंगिक रूपांतरण की थी bool- अभी भी नहीं !)
बैरी

दुर्भाग्य से, यह मेरी वास्तविक समस्या को हल नहीं करता है, लेकिन ऐसा इसलिए था क्योंकि मैं एक एमआरई प्रदान करने में विफल रहा था जो वास्तव में मेरी समस्या का वर्णन करता है। मैं इसे स्वीकार करूंगा और जब मैं अपनी समस्या को ठीक से कम कर पाऊंगा तो एक नया प्रश्न
पूछूंगा

2
मूल मुद्दे के लिए एक उचित कमी की तरह दिखता है gcc.godbolt.org/z/tFy4qz
TC

5

[over.match.best] / 2 यह बताता है कि एक सेट में वैध ओवरलोड को कैसे प्राथमिकता दी जाती है। धारा 2.8 हमें बताती है कि F1क्या बेहतर है F2यदि ( कई अन्य बातों के अलावा):

F2एक लिखित उम्मीदवार ([over.match.oper]) है और F1नहीं है

वहाँ का उदाहरण एक स्पष्ट रूप से पता चलता है, operator<भले ही operator<=>वहाँ है।

और [over.match.oper] /3.4.3 हमें बताता है कि operator==इस परिस्थिति में उम्मीदवारी फिर से लिखने वाले उम्मीदवार की है।

हालांकि , आपके ऑपरेटर एक महत्वपूर्ण बात भूल जाते हैं: वे constकार्य होने चाहिए । और उन्हें constखेलने में आने के लिए अधिभार संकल्प के पहले पहलुओं का कारण नहीं बनता है। न तो फ़ंक्शन एक सटीक मैच है, जैसा कि विभिन्न तर्कों के लिए गैर-से const- constरूपांतरण होना आवश्यक है। यह प्रश्न में अस्पष्टता का कारण बनता है।

एक बार जब आप उन्हें बनाते हैं const, तो क्लैंग ट्रंक संकलित करता है

मैं बाकी Eigen से बात नहीं कर सकता, क्योंकि मुझे कोड की जानकारी नहीं है, यह बहुत बड़ी है, और इस तरह एक MCVE में फिट नहीं हो सकता।


2
यदि आपके पास सभी तर्कों के लिए समान रूप से अच्छे रूपांतरण हैं, तो हम केवल आपके द्वारा सूचीबद्ध टाईब्रेकर तक पहुँच सकते हैं। लेकिन ऐसा नहीं है: लापता होने के कारण const, गैर-उलट उम्मीदवारों के पास दूसरे तर्क के लिए बेहतर रूपांतरण अनुक्रम होता है और उलटे उम्मीदवार के पास पहले तर्क के लिए बेहतर रूपांतरण अनुक्रम होता है।
रिचर्ड स्मिथ

@ रीचर्डस्मिथ: हाँ, यह उस तरह की जटिलता थी जिसके बारे में मैं बात कर रहा था। लेकिन मैं नहीं चाहता था कि वास्तव में उन नियमों के माध्यम से जाना और पढ़ना / आंतरिक करना;)
निकोल बोलस

वास्तव में, मैं constन्यूनतम उदाहरण में भूल गया । मुझे पूरा यकीन है कि ईजन constहर जगह (या बाहर की कक्षा परिभाषाओं का उपयोग करता है, constसंदर्भों के साथ भी ), लेकिन मुझे जांचने की आवश्यकता है। मैं समग्र तंत्र को तोड़ने का प्रयास करता हूं, जब मैं समय पाता हूं तो ईजीन एक न्यूनतम उदाहरण का उपयोग करता है।
18 दिसंबर को chtz

-1

हम अपने Goopax हेडर फ़ाइलों के साथ समान समस्याएँ हैं। क्लैंग -10 और -std = c ++ 2a के साथ निम्नलिखित को संकलित करना एक संकलक त्रुटि पैदा करता है।

template<typename T> class gpu_type;

using gpu_bool     = gpu_type<bool>;
using gpu_int      = gpu_type<int>;

template<typename T>
class gpu_type
{
  friend inline gpu_bool operator==(T a, const gpu_type& b);
  friend inline gpu_bool operator!=(T a, const gpu_type& b);
};

int main()
{
  gpu_int a;
  gpu_bool b = (a == 0);
}

इन अतिरिक्त ऑपरेटरों को प्रदान करने से समस्या का समाधान होता है:

template<typename T>
class gpu_type
{
  ...
  friend inline gpu_bool operator==(const gpu_type& b, T a);
  friend inline gpu_bool operator!=(const gpu_type& b, T a);
};

1
क्या यह कुछ ऐसा नहीं था जो पहले से किया जाना उपयोगी होगा? नहीं तो संकलित कैसे होताa == 0 ?
निकोल बोलस

यह वास्तव में एक समान मुद्दा नहीं है। जैसा कि निकोल ने बताया, यह पहले से ही C ++ 17 में संकलित नहीं था। यह सिर्फ एक अलग कारण से C ++ 20 में संकलित नहीं होता है।
बैरी

मैं उल्लेख करना भूल गया: हम सदस्य ऑपरेटर भी प्रदान करते हैं: gpu_bool gpu_type<T>::operator==(T a) const;और gpu_bool gpu_type<T>::operator!=(T a) const;C ++ - 17 के साथ, यह ठीक काम करता है। लेकिन अब क्लैंग -10 और सी ++ - 20 के साथ, ये अब नहीं पाए जाते हैं, और इसके बजाय कंपाइलर तर्कों को स्वैप करके अपने ऑपरेटरों को उत्पन्न करने की कोशिश करता है, और यह विफल हो जाता है, क्योंकि रिटर्न प्रकार नहीं है bool
इंगो जोसोपैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.