C++ 11: एकाधिक उत्तराधिकार में वर्ग-सदस्य को अस्वीकार करें




c++11 multiple-inheritance (2)

यद्यपि मैं आपको विस्तार से नहीं बता सकता कि यह क्यों काम नहीं करता है, मैंने using Base<int, char>::foo; जोड़ा using Base<int, char>::foo; और using Base<double, void>::foo; Derived और यह अब ठीक संकलित करता है।

clang-3.4 और gcc-4.9 साथ परीक्षण किया गया

मान लीजिए कि मेरे पास यह वैरिएड बेस क्लास-टेम्प्लेट है:

template <typename ... Types>
class Base
{
public:
    // The member foo() can only be called when its template 
    // parameter is contained within the Types ... pack.

    template <typename T>
    typename std::enable_if<Contains<T, Types ...>::value>::type
    foo() {
        std::cout << "Base::foo()\n";
    }
};

foo() सदस्य को तभी बुलाया जा सकता है जब इसका टेम्प्लेट-पैरामीटर Base कम से कम एक पैरामीटर से मेल खाता हो (इस पोस्ट में Contains के कार्यान्वयन को सबसे नीचे सूचीबद्ध किया गया है):

Base<int, char>().foo<int>(); // fine
Base<int, char>().foo<void>(); // error

अब मैं एक व्युत्पन्न वर्ग को परिभाषित करता हूं जो बेस से दो बार विरासत में मिलता है, गैर-अतिव्यापी सेट के प्रकारों का उपयोग करके:

struct Derived: public Base<int, char>,
                public Base<double, void>
{};

मैं उम्मीद कर रहा था कि जब उदाहरण के लिए कॉल करें

Derived().foo<int>();

संकलक यह पता लगाने के लिए कि किस आधार-वर्ग का उपयोग करना है, क्योंकि यह SFINAE'd है जिसमें कोई int नहीं है। हालाँकि, GCC 4.9 और Clang 3.5 दोनों में अस्पष्ट कॉल की शिकायत है।

मेरा सवाल तब दो गुना है:

  1. कंपाइलर इस अस्पष्टता (सामान्य ब्याज) को हल क्यों नहीं कर सकता है?
  2. यह काम करने के लिए मैं क्या कर सकता हूं, बिना Derived().Base<int, char>::foo<int>(); लिखे Derived().Base<int, char>::foo<int>(); ? EDIT: गाइगर ने मुझे दिखाया कि जब मैं दो प्रयोग-घोषणाओं को जोड़ता हूं तो कॉल को अस्वीकृत कर दिया जाता है। हालाँकि, चूंकि मैं उपयोगकर्ता को विरासत से आधार-वर्ग प्रदान कर रहा हूं, इसलिए यह एक आदर्श समाधान नहीं है। यदि संभव हो तो, मैं नहीं चाहता कि मेरे उपयोगकर्ता अपने घोषित वर्गों में उन घोषणाओं (जो बड़ी प्रकार की सूचियों के लिए काफी क्रियात्मक और दोहराव वाले हो सकते हैं) को जोड़ दें।

समास का कार्यान्वयन:

template <typename T, typename ... Pack>
struct Contains;

template <typename T>
struct Contains<T>: public std::false_type
{};

template <typename T, typename ... Pack>
struct Contains<T, T, Pack ...>: public std::true_type
{};

template <typename T, typename U, typename ... Pack>
struct Contains<T, U, Pack ...>: public Contains<T, Pack...>
{};

यहाँ एक सरल उदाहरण है:

template <typename T>
class Base2 {
public:
    void foo(T ) { }
};

struct Derived: public Base2<int>,
                public Base2<double>
{};

int main()
{
    Derived().foo(0); // error
}

इसका कारण मर्ज नियमों से आता है [class.member.lookup]:

अन्यथा (यानी, C में f की घोषणा शामिल नहीं है या परिणामी घोषणा सेट खाली है), S (f, C) शुरू में खाली है। यदि C में बेस क्लास हैं, तो प्रत्येक डायरेक्ट बेस क्लास सबबॉजेक्ट Bi में f के लिए लुकअप सेट की गणना करें, और S (f, C) में बदले में प्रत्येक ऐसे लुकअप सेट S (f, Bi) को मर्ज करें।
- [..]
- अन्यथा, यदि S (f, Bi) और S (f, C) के डिक्लेरेशन सेट अलग-अलग हैं, तो मर्ज अस्पष्ट है ...

चूँकि हमारा प्रारंभिक घोषणा सेट खाली है ( Derived इसकी कोई विधि नहीं है), हमें अपने सभी आधारों से विलय करना होगा - लेकिन हमारे आधारों में अलग-अलग सेट हैं, इसलिए विलय विफल हो जाता है। हालाँकि, यह नियम स्पष्ट रूप से केवल तभी लागू होता है जब C ( Derived ) का घोषणा सेट खाली हो। तो इससे बचने के लिए, हम इसे गैर-रिक्त बनाते हैं:

struct Derived: public Base2<int>,
                public Base2<double>
{
    using Base2<int>::foo;
    using Base2<double>::foo;
};

यह काम करता है क्योंकि using करने के लिए नियम है

घोषणा सेट में, उपयोग-घोषणाओं को नामित सदस्यों के सेट द्वारा प्रतिस्थापित किया जाता है जो कि व्युत्पन्न वर्ग के सदस्यों (7.3.3) द्वारा छिपा या अप्रभावित नहीं हैं,

वहाँ कोई टिप्पणी नहीं है कि क्या सदस्य अलग-अलग हैं या नहीं - हम प्रभावी रूप से सिर्फ दो ओवरलोड के साथ foo पर प्रदान करते हैं, सदस्य नाम लुकअप मर्ज नियमों को दरकिनार करते हैं।

अब, Derived().foo(0) स्पष्ट रूप से Base2<int>::foo(int )

वैकल्पिक रूप से प्रत्येक आधार के लिए स्पष्ट रूप से using करने के बाद, आप उन सभी को करने के लिए एक कलेक्टर लिख सकते हैं:

template <typename... Bases>
struct BaseCollector;

template <typename Base>
struct BaseCollector<Base> : Base
{
    using Base::foo;
};

template <typename Base, typename... Bases>
struct BaseCollector<Base, Bases...> : Base, BaseCollector<Bases...>
{
    using Base::foo;
    using BaseCollector<Bases...>::foo;
};

struct Derived : BaseCollector<Base2<int>, Base2<std::string>>
{ };

int main() {
    Derived().foo(0); // OK
    Derived().foo(std::string("Hello")); // OK
}

C ++ 17 में, आप घोषणाओं का using भी विस्तार using सकते हैं, जिसका अर्थ है कि इसे सरल बनाया जा सकता है:

template <typename... Bases>
struct BaseCollector : Bases...
{
    using Bases::foo...;
};

यह सिर्फ लिखने के लिए छोटा नहीं है, यह संकलन करने के लिए भी अधिक कुशल है। फायदे का सौदा।





enable-if