可变模板中的模糊操作符[]

ambiguous operator[] in variadic template

本文关键字:模糊 操作符      更新时间:2023-10-16

我试图编译这个例子,其中一个可变的类模板从可变的基数继承,每个实现不同的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>]

为什么这是模棱两可的?

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

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<Fields>::operator[]...;,我们就不必这样做了。

使用构造函数时需要注意一些(我没有注意),但您可能不需要这样做。

生活例子。


到底出了什么问题取决于我不太确定的标准细节。基本上,你是以一种复杂的方式混合了操作符、继承和重载。即使你的代码是标准兼容的(它可能是也可能不是),它在某种程度上是兼容的,一些编译器死在

代码无效,gcc拒绝它是正确的(clang 3.6.0接受它-这是一个bug)。查找操作符的规则从[over.match.oper]:

开始

[…]表示二进制文件操作符@,左操作数为cv不限定版本为T1的类型,右操作数为类型其cv- qualified版本为T2,三组候选函数,指定成员候选人非成员候选项内置候选项的构造方法如下:—如果T1是一个完整的类类型或一个正在定义的类,则候选成员集合为T1::operator@(13.3.1.1.1)的限定查找结果;否则,为候选成员集合是空的。

查找成员名的规则是(因为我们查找的是ctmap<last_name,age>::operator[]), from [class.member.lookup]:

查找C中f的集合,称为S(f,C),[…]]是计算出来的如下:

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

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

下面的步骤定义了将查找集S(f,Bi)合并到中间的S(f,C)中的结果:- - - - - -[…]
—否则,如果S(f,Bi)和S(f,C)的声明集不同,则合并是二义性的:newS(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()