سؤال

مرحبًا ، أواجه مشكلات في اختيار الإصدار الصحيح من فئة templated التي لديها تخصص صريح. أرغب في اختيار تخصص باستخدام فئة مشتقة من الفصل المستخدم للتخصص. السيناريو هو:

#include <stdio.h>

class A
{};

class B: public A
{};

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

// Explicit specialization for A
template<> int Foo< A >::FooBar( void ) { return 20; }

void main( void)
{
   Foo<B> fooB;

   // This prints out 10 instead of wanted 20 ie compiler selects the general version
   printf("%d", fooB.FooBar() );
}

كما أقول في تعليقاتي ، أريد أن أرى 20 طبعًا لأن B مشتق من A ولكن يتم طباعة 10 بدلاً من ذلك. كيف يمكنني الحصول على التخصص المسمى بدون اللجوء إلى كتابة تخصص لكل فئة مشتقة (سيناريو الفعلي له كثيراً من الأنواع المشتقة).

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

المحلول

--- تحرير: إجابة جديدة ، دعنا نجعل النهج الأصلي أكثر قابلية للصيانة. يمكن العثور على جميع الخيارات المهمة في تعريف FOO. من المفترض أن يكون من السهل الحفاظ عليها.

#include <boost/mpl/if.hpp>
#include  <boost/type_traits/is_base_of.hpp>
#include <iostream>

class A
{};

class B: public A
{};

class C{};
class D : public C{};
class E{};

struct DefaultMethod
{
    static int fooBar() { return 10; }
};
struct Method1
{
    static int fooBar() { return 20; }
};
struct Method2
{
    static int fooBar() { return 30; }
};

template<typename T, typename BaseClass, typename Choice1, typename OtherChoice>
struct IfDerivesFrom :
    boost::mpl::if_<
        typename boost::is_base_of<BaseClass, T>::type,
        Choice1,
        OtherChoice>::type
{
};

template<typename T>
struct Foo :
    IfDerivesFrom<T, A,
      Method1,
      IfDerivesFrom<T, C,
          Method2,
          DefaultMethod>
      >
{
};

int main()
{
    std::cout << Foo<A>::fooBar() << std::endl;
    std::cout << Foo<B>::fooBar() << std::endl;
    std::cout << Foo<C>::fooBar() << std::endl;
    std::cout << Foo<D>::fooBar() << std::endl;
    std::cout << Foo<E>::fooBar() << std::endl;

    return 0;
}

--- الإجابة الأصلية إذا كنت تستطيع استخدام Boost ، يمكنك القيام بشيء مثل ما يلي:

#include  <boost/type_traits/is_base_of.hpp>

template<bool b>
class FooHelper
{
    int FooBar();
};
template<> FooHelper<true>::FooBar(){ return 20;}
template<> FooHelper<false>::FooBar(){ return 10;}

template<typename T>
class Foo
{
public:
   int FooBar(void) { return FooHelper<boost::is_base_of<A, T>::type::value>(); }
};

نصائح أخرى

بشكل عام ، إنها مشكلة طويلة الأمد مع القالب والميراث بشكل عام.

المشكلة هي أن القالب يعمل على الأنواع الدقيقة ولا تفكر في عامل الميراث ، والفهمين متعامدين إلى حد ما ، وبالتالي يحاول خلط واحد والآخر غالبًا ما يكون عرضة للخطأ.

يمكنك أيضًا التحقق من ذلك بالطرق:

template <class T>
int fooBar(T) { return 10; }

int fooBar(A) { return 20; }

B b;
fooBar(b); // this returns 10, because fooBar<T> is a better match (no conversion)

الآن ، على مشاكلك ، بينما أقدر الحلول المختلفة التي تم تقديمها باستخدام enable_if و is_base_of الحيل ، أنا أتجاهلهم على أنها غير عملية. الهدف من التخصص هو أن مؤلف كتاب Foo لا يجب أن تعرف كيف سيتخصص أي شخص في فصلها إذا لزم الأمر ، فقط لتسهيل الأمر. وإلا إذا كنت بحاجة إلى عشرات التخصص ، ينتهي بك الأمر إلى غريب للغاية Foo الفصل ، هذا بالتأكيد.

وقد تعاملت STL بالفعل مع مشاكل مماثلة. عادة ما يكون المصطلح المقبول هو توفير فئة السمات. توفر فئة السمات الافتراضية حلاً جيدًا للجميع بينما يمكن للمرء أن يتخصص في فئة السمات لاستيعاب حاجة المرء.

أعتقد أنه يجب أن تكون هناك طريقة باستخدام المفاهيم (أي ، إذا كان T يحدد t :: foobar () ثم يستخدمه ، وإلا فإن الإصدار الافتراضي ...) ، ولكن بالنسبة للموضة الزائدة ، فهذا غير مطلوب.

namespace detail { int fooBar(...) { return 10; } }

template <class T>
class Foo
{
public:
  static int FooBar() { T* t(0); return ::detail::fooBar(t); }
};

والآن ، للتخصص في الفصول المشتقة من:

namespace detail { int fooBar(A*) { return 20; } }

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

بعض الاعتبارات:

  • مساحة الاسم: اعتمادًا على المعرف fooBar من المحتمل أن يتم استخدامه أم لا ، فقد تفضل عزل مساحة اسم خاصة بها (أو مكرسة لـ Foo الفصل) ، خلاف ذلك ، قم بإجراء مكالمة غير مؤهلة ودع المستخدم يحددها في مساحة اسم فئةها.

  • هذه الخدعة تعمل فقط من أجل الاحتجاج بالميراث والطريقة ، فهي لا تعمل إذا كنت ترغب في جلب typedefs الخاصة

  • يمكنك تمرير المزيد من القوالب إلى الطريقة الفعلية ، مثل النوع الحقيقي

هنا مثال على وظائف القالب

namespace detail { template <class T> int fooBar(...) { return 10; } }

template <class T>
int Foo<T>::FooBar() { T* t(0); return ::detail::fooBar<T>(t); }

namespace detail {
  template <class T>
  int fooBar(A*)
  {
    return T::FooBar();
  }
}

وهنا ما سيحدث:

struct None {};
struct A { static int FooBar() { return 20; } };
struct B: A {};
struct C: A { static int FooBar() { return 30; } };

int main(int argc, char* argv[])
{
  std::cout << Foo<None>::FooBar()  // prints 10
     << " " << Foo<A>::FooBar()     // prints 20
     << " " << Foo<B>::FooBar()     // prints 20
     << " " << Foo<C>::FooBar()     // prints 30
     << std::endl;
}

النقطة الأولى (الصغرى): عنوانك غير صحيح ؛ هذا هو التخصص الصريح ، وليس التخصص الجزئي. للحصول على التخصص الجزئي ، تحتاج إلى تحديد معلمة قالب واحد على الأقل ، ولكن ترك واحد على الأقل غير محدد:

template <class T, class U>
demo { };

template <class T>
demo<int> {};  // use in the case of demo<XXX, int> 

بالنظر إلى الكود الخاص بك ، أنا مندهش قليلاً من أنه يجمع على الإطلاق. لست متأكدًا من أن هناك أي طريقة يمكن أن تجبر وظيفتك المتخصصة على الاتصال بها. عادة ، كنت متخصصًا في الفصل ككل:

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

template<>
class Foo<A> {
public:
    int FooBar() { return 20; }
};

في هذه الحالة ، لن يكون هذا جيدًا حقًا. يمكنك تحويل كائن مشتق إلى كائن أساسي ضمنيًا ، ولكنه هو لا يزال التحويل. من ناحية أخرى ، يمكن استخدام النسخة غير المتخصصة من القالب مع رقم التحويل - وعند اختيار أي واحد لاستخدامه ، يعامل برنامج التحويل البرمجي الذي يمكن إنشاء مثيل له بدون تحويل كخيار أفضل من الاختيار الذي يتطلب تحويلًا ضمنيًا.

إليك حل ، لكنه ليس لطيفًا بشكل خاص:

template<typename T>
class Foo
{
public:
  int FooBar(typename disable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 10; }
  int FooBar(typename enable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 20; }
};

تحتاج إلى التخصص في النوع الدقيق. فمثلا Foo<A> fooA; fooA.FooBar(); سوف تحصل على 20. او استعمل BOOST.TYPE_TRAITS كما يظهر @بينوا.

في الأساس ، تريد أن يكون لديك قالب تخصص في الفصول المشتقة.

إذا لم تكن بحاجة إلى أن تكون الفئة الفعلية متخصصة ، فقط الوظائف (الوظائف) ، فيبدو أنك تستطيع القيام بها فقط:

int foo::foobar(A &someA);

إذا كنت بحاجة إلى أن يكون الفصل متخصصًا فعليًا ، فأعتقد أنك تريد أن تنظر إلى واجهات ونمط بيانات الفصل الخاص ؛ أكثر أو أقل ، أن الواجهة "تقلل" كائن إلى نوع معترف به من النوع المعترف به ، ثم يستدعي ؛ علاء

int foo::foobar(A &someA)
{ return fooImpl<A>::foobar(someA); }

لكنني أفترض أن هذا لا يجيب حقًا على سؤالك ، لأنه لا يدعم الحالة العامة. أفترض أنه يمكن أن يكون لديك:

template<class T>
class foo
{
public: 
    int foobar(T &t);
    int foobar(A &a);
}

foo<A>::foobar(someA);
foo<F>::foobar(someA);
foo<not B>::foobar(someB); //Should trigger foobar(A &a), right?

هذا سيكون قادرًا على التعرف على B كما هو مستمد من A ، مع الاستمرار في توفير الحالة العامة. أظن؛ لم أختبر هذا.

إنها ليست أجمل ، لكنني أعتقد أن لديك بعض الفرص لبعض الأشياء التي تشبه التحكم في الوصول إلى هناك ، إذا كنت متخصصًا في الفصل الفعلي ، حيث يمكنك تضمين أو عدم تضمين العديد من Foobar (A & A) مثل وظائف السماح أو رفض الاستخدام على أشجار الميراث المختلفة ؛ للمثال أعلاه ؛

foo<C>::foobar(someF); //doesn't exist!
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top