【问题标题】:ambiguous operator[] in variadic template可变参数模板中的歧义运算符 []
【发布时间】:2015-07-20 20:09:15
【问题描述】:

我正在尝试编译这个示例,其中可变参数类模板继承自可变数量的基,每个基都实现不同的operator[]

#include <iostream>

template <typename T>
struct Field {
  typename T::value_type storage;

  typename T::value_type &operator[](const T &c) {
    return storage;
  }
};

template<typename... Fields>
struct ctmap : public Field<Fields>... {
};

int main() {
    struct age { typedef int value_type; };
    struct last_name { typedef std::string value_type; };

    ctmap<last_name, age> person;

    person[last_name()] = "Smith";
    person[age()] = 104;
    std::cout << "Hello World!" << std::endl;
    return 0;
}

当我使用 gcc (Debian 4.9.2-10) 编译时,出现以下错误

main.cpp: In function ‘int main()’:
main.cpp:22:23: error: request for member ‘operator[]’ is ambiguous
     person[last_name()] = "Smith";
                       ^
main.cpp:7:27: note: candidates are: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::age; typename T::value_type = int]
   typename T::value_type &operator[](const T &c) {
                           ^
main.cpp:7:27: note:                 typename T::value_type& Field<T>::operator[](const T&) [with T = main()::last_name; typename T::value_type = std::basic_string<char>]
main.cpp:23:17: error: request for member ‘operator[]’ is ambiguous
     person[age()] = 104;
                 ^
main.cpp:7:27: note: candidates are: typename T::value_type& Field<T>::operator[](const T&) [with T = main()::age; typename T::value_type = int]
   typename T::value_type &operator[](const T &c) {
                           ^
main.cpp:7:27: note:                 typename T::value_type& Field<T>::operator[](const T&) [with T = main()::last_name; typename T::value_type = std::basic_string<char>]

为什么这是模棱两可的?

【问题讨论】:

  • 这被clang++3.8接受:melpon.org/wandbox/permlink/huzwGp0kc2OafMZl(但我不确定在这种情况下哪个编译器是正确的)
  • (基本上多个基类中的成员函数不会重载。不过我不确定这如何与运算符查找交互;乍一看,clang 似乎是错误的。)
  • 您可以使其符合基于线性/树的继承和using 声明。
  • 是真正的dyp,我用clang 3.6在coliru中尝试这个代码并且工作正常,但我需要使用gcc,在这种情况下版本是4.9.2。
  • Yakk,你能解释一下如何进行线性/树基继承吗?你能举个例子吗?

标签: c++ c++11 operator-overloading variadic-templates ambiguous


【解决方案1】:

一种可移植的方式做你想做的事情大致是:

template<class...Ts>
struct operator_index_inherit {};
template<class T0, class T1, class...Ts>
struct operator_index_inherit<T0, T1, Ts...>:
  T0, operator_index_inherit<T1, Ts...>
{
  using T0::operator[];
  using operator_index_inherit<T1, Ts...>::operator[];
};
template<class T0>
struct operator_index_inherit<T0>:
  T0
{
  using T0::operator[];
};

然后:

template<class... Fields>
struct ctmap : operator_index_inherit<Field<Fields>...> {
  using base = operator_index_inherit<Field<Fields>...>;
  using base::operator[];
};

这里我们从每个类型线性继承,using operator[] 在我们的父母身上。

如果我们可以using Field&lt;Fields&gt;::operator[]...;,我们就不必这样做了。

必须注意构造函数(我没有这样做),但您可能不需要这样做。

live example.


实际出了什么问题取决于我不太确定的标准的细节。基本上,您正在以一种复杂的方式混合运算符、继承和重载。即使您的代码符合标准(它可能符合也可能不符合),它还是符合某些编译器的方式。

【讨论】:

【解决方案2】:

代码无效,gcc 拒绝它是正确的(不过,clang 3.6.0 接受它 - 这是一个错误)。查找运算符的规则从 [over.match.oper] 开始:

[...] 用于二进制 运算符@,其左操作数为 cv 非限定版本为 T1,右操作数为 其cv-unqualified版本为T2,三组候选函数,指定成员候选非成员 候选项内置候选项,构造如下:
— 如果 T1 是完整的类类型或当前正在定义的类,则成员候选集是 T1::operator@ (13.3.1.1.1) 的合格查找结果;否则,成员候选集 是空的。

成员名称的查找规则是(因为我们正在查找 ctmap&lt;last_name,age&gt;::operator[]),来自 [class.member.lookup]:

计算 C 中 f 的查找集,称为 S(f,C),[...] 如下:

如果 C 包含名称 f 的声明,[...]

否则(即 C 不包含 f 的声明或结果声明集为空),S(f,C) 为 最初是空的。如果 C 有基类,计算 f 在每个直接基类子对象 Bi 中的查找集, 并将每个这样的查找集 S(f,Bi) 依次合并为 S(f,C)。

以下步骤定义了将查找集 S(f,Bi) 合并到中间 S(f,C) 中的结果:
— [...]
— 否则,如果 S(f,Bi) 和 S(f,C) 的声明集不同,则合并是不明确的:新的 S(f,C) 是具有无效声明集和子对象集并集的查找集。在随后的 合并,无效的声明集被认为与其他任何声明集不同。
— [...]

基本上 - 我们这里有两个基类,都带有operator[]。两个声明集不同 - 所以合并是模棱两可的。消除歧义的方法是引入 using-declaration 将所有基类成员函数引入派生类,以便初始查找集找到所有内容。

为了缩短你的例子:

struct A { void foo(char) { } };
struct B { void foo(int ) { } };

struct C : A, B { };

struct D : A, B {
    using A::foo;
    using B::foo;
};

具有这种层次结构

C c;
c.foo(4);  // error: ambiguous lookup set for foo()

D d;
d.foo('x') // OK: calls A::foo()

【讨论】:

  • 我明白....我有一个问题,如何调用 B::foo()? ...我尝试强制转换“4”,或者声明一个变量 int,但总是给出模棱两可的查找。
  • @Trungus 参数无关紧要 - 名称 foo 的实际查找失败,因为它不明确。要调用它,您需要有 using-declaration(类中的using B::foo)。或者你可以通过指向成员的指针来调用它((C{}.*&amp;B::foo)(4);
  • 如果我错了,请纠正我,但您的示例已经在类声明中包含“使用 B:foo”,为什么仍然存在模棱两可的查找错误?
  • @Trungus Dusings,并且没有错误。 C 省略了它们,因此有错误。
  • @Yakk [over.match.oper] 首先将候选集构建为“如果 T1 是完整的类类型或当前正在定义的类,则成员候选集是T1::operator@ (13.3.1.1.1) 的合格查找;否则,成员候选集为空。"所以我们仍然遵循成员查找规则 - 但我们只是添加匹配的非成员。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-24
  • 1970-01-01
  • 1970-01-01
  • 2022-06-12
  • 2010-09-27
  • 2020-04-22
相关资源
最近更新 更多