可变参数模板

时间:2015-07-20 20:09:16

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

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

为什么这个含糊不清?

2 个答案:

答案 0 :(得分:3)

便携式方式可以做到你想要的大致:

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

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

live example

实际上出错的地方取决于我不太确定的标准细节。基本上,您是以复杂的方式混合运算符,继承和重载。即使您的代码符合标准(它可能是也可能不是),它的符合性会使某些编译器死亡。

答案 1 :(得分:2)

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

  

[...]二进制文件   operator @,其左侧操作数的类型为cv非限定版本为T1,右操作数为类型   其cv不合格版本为T2,三组候选函数,指定成员候选人非成员   候选人内置候选人的构建如下:
   - 如果T1是完整的类类型或当前正在定义的类,则成员候选者集合是   T1::operator@的合格查询结果(13.3.1.1.1);否则,成员候选人   是空的。

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

  

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

     

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

     

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

     

以下步骤定义将查找集S(f,B i )合并到中间体S(f,C)中的结果:
   - [...]
   - 否则,如果S(f,B i )和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()