在多重继承中消除 class-成员的歧义
Disambiguate class-member in multiple inheritance
假设我有这个可变参数基础class-模板:
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
的实现列在底部post):
Base<int, char>().foo<int>(); // fine
Base<int, char>().foo<void>(); // error
现在我定义了一个从 Base 继承两次的派生 class,使用 非重叠集 类型:
struct Derived: public Base<int, char>,
public Base<double, void>
{};
我希望在打电话时,例如
Derived().foo<int>();
编译器会找出要使用的基数-class,因为它是 SFINAE 中不包含 int
的基数。然而,GCC 4.9 和 Clang 3.5 都抱怨调用不明确。
那么我的问题有两个:
- 为什么编译器不能解决这个歧义(一般兴趣)?
- 我可以做些什么来完成这项工作,而不必编写
Derived().Base<int, char>::foo<int>();
? 编辑: GuyGreer 向我展示了当我添加两个 using 声明时该调用已消除歧义。但是,由于我提供了 base-class 供用户继承,这不是一个理想的解决方案。如果可能的话,我不希望我的用户必须将这些声明(对于大型类型列表来说可能非常冗长和重复)添加到他们派生的 classes.
实施Contains
:
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...>
{};
虽然我不能详细告诉你为什么它不能按原样工作,但我在 Derived
中添加了 using Base<int, char>::foo;
和 using Base<double, void>::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
}
原因来自于合并规则[class.member.lookup]:
Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is
initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi,
and merge each such lookup set S(f,Bi) in turn into S(f,C).
— [..]
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous...
由于我们的初始声明集是空的(Derived
中没有方法),我们必须从我们所有的基础中合并——但是我们的基础有不同的集合,所以合并失败。但是,该规则仅在 C
(Derived
) 的声明集为空时明确适用。所以为了避免它,我们让它非空:
struct Derived: public Base2<int>,
public Base2<double>
{
using Base2<int>::foo;
using Base2<double>::foo;
};
之所以可行,是因为应用 using
的规则是
In the declaration set, using-declarations are replaced by the set
of designated members that are not hidden or overridden by members of the derived class (7.3.3),
那里没有关于成员是否不同的评论 - 我们实际上只是为 Derived
提供 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中,你也可以pack expand using
declarations,这意味着这可以简化为:
template <typename... Bases>
struct BaseCollector : Bases...
{
using Bases::foo...;
};
这不仅编写起来更短,编译起来也更高效。双赢。
假设我有这个可变参数基础class-模板:
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
的实现列在底部post):
Base<int, char>().foo<int>(); // fine
Base<int, char>().foo<void>(); // error
现在我定义了一个从 Base 继承两次的派生 class,使用 非重叠集 类型:
struct Derived: public Base<int, char>,
public Base<double, void>
{};
我希望在打电话时,例如
Derived().foo<int>();
编译器会找出要使用的基数-class,因为它是 SFINAE 中不包含 int
的基数。然而,GCC 4.9 和 Clang 3.5 都抱怨调用不明确。
那么我的问题有两个:
- 为什么编译器不能解决这个歧义(一般兴趣)?
- 我可以做些什么来完成这项工作,而不必编写
Derived().Base<int, char>::foo<int>();
? 编辑: GuyGreer 向我展示了当我添加两个 using 声明时该调用已消除歧义。但是,由于我提供了 base-class 供用户继承,这不是一个理想的解决方案。如果可能的话,我不希望我的用户必须将这些声明(对于大型类型列表来说可能非常冗长和重复)添加到他们派生的 classes.
实施Contains
:
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...>
{};
虽然我不能详细告诉你为什么它不能按原样工作,但我在 Derived
中添加了 using Base<int, char>::foo;
和 using Base<double, void>::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
}
原因来自于合并规则[class.member.lookup]:
Otherwise (i.e., C does not contain a declaration of f or the resulting declaration set is empty), S(f,C) is initially empty. If C has base classes, calculate the lookup set for f in each direct base class subobject Bi, and merge each such lookup set S(f,Bi) in turn into S(f,C).
— [..]
— Otherwise, if the declaration sets of S(f,Bi) and S(f,C) differ, the merge is ambiguous...
由于我们的初始声明集是空的(Derived
中没有方法),我们必须从我们所有的基础中合并——但是我们的基础有不同的集合,所以合并失败。但是,该规则仅在 C
(Derived
) 的声明集为空时明确适用。所以为了避免它,我们让它非空:
struct Derived: public Base2<int>,
public Base2<double>
{
using Base2<int>::foo;
using Base2<double>::foo;
};
之所以可行,是因为应用 using
的规则是
In the declaration set, using-declarations are replaced by the set of designated members that are not hidden or overridden by members of the derived class (7.3.3),
那里没有关于成员是否不同的评论 - 我们实际上只是为 Derived
提供 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中,你也可以pack expand using
declarations,这意味着这可以简化为:
template <typename... Bases>
struct BaseCollector : Bases...
{
using Bases::foo...;
};
这不仅编写起来更短,编译起来也更高效。双赢。