C++ 多重继承中类成员的消歧

C++ 多重继承中类成员的消歧,c++,c++11,multiple-inheritance,ambiguity,enable-if,C++,C++11,Multiple Inheritance,Ambiguity,Enable If,假设我有这个可变基类模板: 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

假设我有这个可变基类模板:

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";
    }
};
现在,我定义了一个派生类,它使用类型的非重叠集从基继承两次:

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

虽然我不能详细地告诉您为什么它不能按原样工作,但我使用Base::foo添加了
使用Base::foo
派生的
,它现在编译得很好


使用
clang-3.4
gcc-4.9

进行测试,下面是一个简单的示例:

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

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

int main()
{
    Derived().foo(0); // error
}
这是因为应用
使用
的规则是

在声明集中,使用声明将替换为声明集 未被派生类成员隐藏或覆盖的指定成员(7.3.3)

这里没有关于成员是否不同的评论-我们实际上只是在
foo
上提供了两个重载,绕过了成员名称查找合并规则

现在,
Derived().foo(0)
明确地调用
Base2::foo(int)


除了为每个基显式使用
之外,您还可以编写一个收集器来完成所有任务:

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
}

这不仅缩短了编写时间,而且提高了编译效率。双赢

这很有趣+1.但是,如果可能的话,我不希望我的用户在从我提供的基类继承时必须将那些使用声明的人添加到他们的代码中。尽管如此,还是要谢谢你@JorenHeit我不知道为什么从
Base
派生两次是有意义的。当然它们是不重叠的,但为什么不直接从
Base
派生呢?@GuyGreer我正在为用户提供另一个类,我们称之为
Special
,它本身是从
Base
派生的,使用保留类型(对用户隐藏,因此保证用户不会使用此类型,因此不重叠)。如果可以同时从
Special
Base
@JorenHeit派生,那就太好了。您可以始终提供一个
SpecialAndBase
派生自
Special
Base
,并使用
s添加适当的
。@T.C.Hm。。。事实上这还不错。很明显。。。谢谢这是一个普遍规则,不同范围的名称在C++中不超载。谢谢,我理解歧义来自何处。然而,正如我在对GuyGreer的回答的评论和编辑的问题中提到的,这个解决方案对我的用户不是很友好。我想,如果没有简单的解决方法,我就必须仔细记录。@JorenHeit如果你愿意在层次结构中引入一个类,一个收集一组
Base
实例化的类,并让用户从中继承,你可以让它工作。@jrok但是我必须知道
Base
将被实例化的类型,不是吗?@JorenHeit提供了一个解决方案,可以使用
为你完成所有的
。@Barry你不应该也从
BaseCollector
引入
foo
吗?还需要一些东西来停止递归继承。
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
}
struct Derived: public Base2<int>,
                public Base2<double>
{
    using Base2<int>::foo;
    using Base2<double>::foo;
};
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
}
template <typename... Bases>
struct BaseCollector : Bases...
{
    using Bases::foo...;
};