14

我正在尝试[]为一个类实现类似矢量和类似地图的运算符。但我从我的编译器(g++ 和 clang++)收到错误消息。发现它们仅在该类还具有将运算符转换为整数类型时才会发生。

现在我有两个问题。首先是我不知道为什么编译器无法区分[](const std::string&)以及[](size_t)何时类具有转换运算符到整数。第二个......我需要转换和索引运算符。如何解决?

作品:

#include <stdint.h>
#include <string>

struct Foo
{
    Foo& operator[](const std::string &foo) {}
    Foo& operator[](size_t index) {}
};

int main()
{
    Foo f;
    f["foo"];
    f[2];
}

不起作用:

#include <stdint.h>
#include <string>

struct Foo
{
    operator uint32_t() {}
    Foo& operator[](const std::string &foo) {}
    Foo& operator[](size_t index) {}
};

int main()
{
    Foo f;
    f["foo"];
    f[2];
}

编译器错误:

main.cpp: In function 'int main()':
main.cpp:14:9: error: ambiguous overload for 'operator[]' in 'f["foo"]'
main.cpp:14:9: note: candidates are:
main.cpp:14:9: note: operator[](long int, const char*) <built-in>
main.cpp:7:7: note: Foo& Foo::operator[](const string&)
main.cpp:8:7: note: Foo& Foo::operator[](size_t) <near match>
main.cpp:8:7: note:   no known conversion for argument 1 from 'const char [4]' to 'size_t {aka long unsigned int}'
4

3 回答 3

20

问题是你的类有一个转换运算符 to uint32_t,所以编译器不知道是否:

  1. 从字符串文字构造 astd::string并调用接受 a 的重载std::string
  2. 将您的Foo对象转换为 anuint32_t并将其用作字符串文字的索引。

虽然选项 2 可能听起来令人困惑,但请考虑以下表达式在 C++ 中是合法的:

1["foo"];

这是因为内置的下标运算符是如何定义的。根据 C++11 标准的第 8.3.4/6 段:

除了已为类 (13.5.5) 声明的情况外,下标运算符 [] 的解释方式E1[E2]*((E1)+(E2)). 由于适用于 + 的转换规则,如果E1是数组和E2整数,则E1[E2]指的是 的E2第 - 个成员E1。因此,尽管它的外观不对称,但下标是一种可交换操作

因此,上述表达式1["foo"]等价于"foo"[1],其计算结果为o。要解决歧义,您可以制作转换运算符explicit(在 C++11 中):

struct Foo
{
    explicit operator uint32_t() { /* ... */ }
//  ^^^^^^^^
};

或者您可以保留该转换运算符,并std::string显式构造对象:

    f[std::string("foo")];
//    ^^^^^^^^^^^^     ^

或者,您可以添加接受 a 的下标运算符的进一步重载const char*,这将比上述任何一个更好的匹配(因为它不需要用户定义的转换):

struct Foo
{
    operator uint32_t() { /* ... */ }
    Foo& operator[](const std::string &foo) { /* ... */ }
    Foo& operator[](size_t index) { /* ... */ }
    Foo& operator[](const char* foo) { /* ... */ }
    //              ^^^^^^^^^^^
};

另请注意,您的函数具有非 void 返回类型,但当前缺少一条return语句。这会在您的程序中注入未定义的行为。

于 2013-04-06T12:23:49.390 回答
3

问题是f["foo"]可以解决为:

  1. 转换"foo"std::string(be it s) 并f[s]调用Foo::operator[](const std::string&).
  2. 转换f为整数调用Foo::operator int()(be it i)并i["foo"]使用众所周知的事实,即内置[]运算符是可交换的。

两者都有一种自定义类型转换,因此存在歧义。

简单的解决方案是添加另一个重载:

Foo& operator[](const char *foo) {}

现在,调用f["foo"]将调用新的重载而不需要任何自定义类型转换,因此歧义被打破。

注意:从 type char[4](type type of "foo") 到的转换char*被认为是微不足道的,不算数。

于 2013-04-06T12:24:08.923 回答
2

如其他答案所述,您的问题是[]默认情况下通勤 -与fora[b]相同,并且您的班级可转换为this 与转换为.b[a]char const*uint32_tchar*std::string

我在这里提供的是一种“极具吸引力的重载”的方法,当您遇到这种问题时,尽管您认为应该调用重载,但它不会被调用。

所以这里Foo有一个“极具吸引力的超载” std::string

struct Foo
{
  operator uint32_t() {return 1;}
  Foo& lookup_by_string(const std::string &foo) { return *this; }
  Foo& operator[](size_t index) {return *this;}
  template<
    typename String,
    typename=typename std::enable_if<
      std::is_convertible< String, std::string >::value
    >::type
  > Foo& operator[]( String&& str ) {
    return lookup_by_string( std::forward<String>(str) );
  }
};

我们在其中创建了一个独立的“按字符串查找”函数,然后编写一个模板来捕获任何可以转换为std::string.

因为它将用户定义的转换“隐藏”在模板的主体中operator[],所以在检查匹配时不会发生用户定义的转换,因此这比其他需要用户定义的转换的操作(如uint32_t[char*])更可取。实际上,这是一个比任何不完全匹配参数的重载“更有吸引力”的重载。

这可能会导致问题,如果您有另一个需要 aconst Bar&Bar转换为std::string的重载,则上述重载可能会让您感到惊讶并捕获传入的Bar内容——右值和非 const 变量[]都比[const Bar&]!

于 2013-04-06T13:30:49.350 回答