سؤال

كان السؤال الأخير الذي طرحته هو شيء تعثرت عليه عند محاولة فهم شيء آخر ... لا يمكنني فهمه أيضًا (وليس يومي).

هذا بيان سؤال طويل ، لكنني على الأقل آمل أن يكون هذا السؤال مفيدًا لكثير من الناس وليس فقط.

الرمز الذي لدي هو ما يلي:

template <typename T> class V;
template <typename T> class S;

template <typename T>
class V
{
public:
 T x;

 explicit V(const T & _x)
 :x(_x){}

 V(const S<T> & s)
 :x(s.x){}
};

template <typename T>
class S
{
public:
 T &x;

 explicit S(V<T> & v)
 :x(v.x)
 {}
};

template <typename T>
V<T> operator+(const V<T> & a, const V<T> & b)
{
 return V<T>(a.x + b.x);
}

int main()
{
 V<float> a(1);
 V<float> b(2);
 S<float> c( b );

 b = a + V<float>(c); // 1 -- compiles
 b = a + c;           // 2 -- fails
 b = c;               // 3 -- compiles

 return 0;
}

التعبيرات 1 و 3 تعمل بشكل مثالي ، في حين أن التعبير 2 لا يجمع.

إذا فهمت بشكل صحيح ، ما يحدث هو:

التعبير 1

  1. ج يتم تحويله ضمنيًا إلى const باستخدام تسلسل تحويل قياسي (يتألف على واحد فقط تحويل التأهيل).
  2. V<float>(const S<T> & s) يسمى وزملا const V<float> تم إنشاء الكائن (دعنا نسميها ر). إنه مؤهل بالفعل لأنه قيمة زمنية.
  3. أ يتم تحويله إلى مشابه ل ج.
  4. operator+(const V<float> & a, const V<float> & b) يسمى ، مما أدى إلى زمن من النوع const V<float> أنه يمكننا الاتصال س.
  5. الافتراضي V<float>::operator=(const & V<float>) يسمى.

هل أنا موافق هنا؟ إذا ارتكبت أكثر من أخطاء خفيفة من فضلك ، فأخبرني ، لأنني أحاول أن أكسب فهمًا حول الأداء الضمني بعمق قدر الإمكان ...

التعبير 3

  1. ج يتم تحويله إلى V<float>. لذلك ، لدينا تسلسل تحويل محدد من قبل المستخدم:
    1.1. التحويل القياسي الأول: S<float> إلى const S<float> عن طريق تحويل التأهيل.
    1.2. التحويل المعرفة من قبل المستخدم: const S<float> إلى V<float> عبر V<float>(const S<T> & s) البناء.
    1.3 التحويل القياسي الثاني: V<float> إلى const V<float> عن طريق تحويل التأهيل.
  2. الافتراضي V<float>::operator=(const & V<float>) يسمى.

التعبير 2؟

ما لا أفهمه هو سبب وجود مشكلة في التعبير الثاني. لماذا التسلسل التالي غير ممكن؟

  1. ج يتم تحويله إلى V<float>. لذلك ، لدينا تسلسل تحويل محدد من قبل المستخدم:
    1.1. التحويل القياسي الأولي: S<float> إلى const S<float> عن طريق تحويل التأهيل.
    1.2. التحويل المعرفة من قبل المستخدم: const S<float> إلى V<float> عبر V<float>(const S<T> & s) البناء.
    1.3. التحويل القياسي النهائي: V<float> إلى const V<float> عن طريق تحويل التأهيل.
  2. الخطوات من 2 إلى 6 هي نفسها كما في حالة التعبير 1.

بعد قراءة معيار C ++ أنا على الرغم من: "مهلا! ربما يجب أن تكون المشكلة مع 13.3.3.1.2.3! التي تنص على:

إذا تم تحديد التحويل المعرفة من قبل المستخدم بواسطة وظيفة تحويل القالب ، فيجب أن يكون لسلسلة التحويل القياسية الثانية مرتبة مطابقة دقيقة.

ولكن هذا لا يمكن أن يكون هو الحال لأن تحويل التأهيل له رتبة مطابقة بالضبط ...

أنا حقا لا أملك دليلا...

حسنًا ، سواء كان لديك إجابة أم لا ، شكرًا لك على القراءة إلى هنا :)

هل كانت مفيدة؟

المحلول

كما أشار Edric ، لا يتم النظر في التحويلات أثناء خصم حجة القالب. هنا ، لديك سياقان حيث يمكن استنتاج معلمة القالب T من نوع الوسائط:

template<class T>
v<T> operator+(V<T> const&, V<T> const&);
               ~~~~~~~~~~~  ~~~~~~~~~~~~

لكنك تحاول استدعاء قالب الوظيفة هذا مع V<float> على الجانب الأيسر و S على الجانب الأيمن. ينتج عن خصم الوسيطة القالب t = تعويم على الجانب الأيسر وسيحصل على خطأ في الجانب الأيمن لأنه لا يوجد t لذلك V<T> يساوي S<T>. هذا مؤهل كفشل في خصم حجة القالب ويتم تجاهل القالب ببساطة.

إذا كنت ترغب في السماح للتحويلات ، فلا ينبغي أن يكون مشغلك+ قالبًا. هناك الحيلة التالية: يمكنك تعريفها كصديق مضمّن داخل قالب الفصل لـ V:

template<class T>
class V
{
public:
   V();
   V(S<T> const&); // <-- note: no explicit keyword here

   friend V<T> operator+(V<T> const& lhs, V<T> const& rhs) {
      ...
   }
};

بهذه الطريقة ، لم يعد المشغل قالبًا بعد الآن. لذلك ، ليست هناك حاجة لخصم الوسيطة القالب وينبغي العمل الاحتجاجية. تم العثور على المشغل من خلال ADL (البحث المعتمد على الوسيطة) لأن الجانب الأيسر هو أ V<float>. يتم تحويل الجانب الأيمن بشكل صحيح إلى أ V<float> كذلك.

من الممكن أيضًا تعطيل خصم الحجة في القالب لحجة محددة. فمثلا:

template<class T>
struct id {typedef T type;};

template<class T>
T clip(
   typename id<T>::type min,
   T value,
   typename id<T>::type max )
{
   if (value<min) value=min;
   if (value>max) value=max;
   return value;
}

int main() {
   double x = 3.14;
   double y = clip(1,x,3); // works, T=double
}

على الرغم من أن نوع الحجة الأولى والأخيرة هو INT ، إلا أنه لا يتم النظر فيه أثناء خصم حجة القالب بسبب id<T>::type ليس ما يسمى *سياق استنتاجي. لذلك ، يتم استنتاج T فقط وفقًا للوسيطة الثانية ، والتي تؤدي إلى t = مضاعفة بدون تناقضات.

نصائح أخرى

عند النظر في مباريات القالب ، لا يتم استخدام التحويلات الضمنية. لذلك ، في المثال البسيط التالي:

template < typename T >
void foo( T t1, T t2 ) { /* do stuff */ }

int main( int argc, char ** argv ) {
    foo( 1, 1.0 );
    return 0;
}

لن يتم تجميع ذلك على الرغم من أنه يمكن تحويل أي من الوسيطة ضمنيًا إلى النوع الآخر (int <-> double).

مجرد تخمين ، ولكن ربما لا يمكن للمترجم التمييز بين التحويل من V-> s أو من S-> V أثناء محاولة معرفة كيفية إضافة A + C في التعبير 2. أنت تفترض أن المترجم سيكون ذكيًا بما يكفي اختر واحد الذي يسمح للتجميع بالمتابعة بسبب بقية الوظائف المتاحة ، ولكن ربما لا يكون المترجم "يقرأ إلى الأمام" (إذا جاز التعبير) ، ويتم الخلط عامل "+".

بالطبع ، إذا أضفت خطأ التجميع ، فقد يساعد ذلك في توضيح المشكلة أيضًا ...

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top