我试图像类的[]运算符一样实现类似的向量。但我收到编译器的错误消息(g ++和clang ++)。发现只有当类还将转换运算符转换为整数类型时才会出现它们。
现在我有两个问题。首先,我不知道为什么编译器可以区分[](const std :: string&)和类当转换运算符为int时。 第二个......我需要转换和索引运算符。有谁知道如何解决这个问题?
提前感谢并向我致以最诚挚的问候
工作的:
#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}'
答案 0 :(得分:20)
问题是你的类有一个转换运算符uint32_t
,所以编译器不知道是否:
std::string
并调用您的重载接受std::string
; Foo
对象转换为uint32_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")];
// ^^^^^^^^^^^^ ^
或者,您可以添加接受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
语句。这会在您的程序中注入未定义的行为。
答案 1 :(得分:3)
问题是f["foo"]
可以解析为:
"foo"
转换为std::string
(是s
)并f[s]
拨打Foo::operator[](const std::string&)
。f
转换为整数调用Foo::operator int()
(是i
)并使用内置i["foo"]
运算符可交换的众所周知的事实[]
两者都有一个自定义类型转换,因此含糊不清。
简单的解决方案是添加另一个重载:
Foo& operator[](const char *foo) {}
现在,调用f["foo"]
将调用新的重载而不需要任何自定义类型转换,因此模糊性会被破坏。
注意:从char[4]
类型("foo"
的类型类型)到char*
的转换被认为是微不足道的,不计算在内。
答案 2 :(得分:2)
正如其他答案所述,您的问题是默认情况下[]
次通勤 - a[b]
与b[a]
的{{1}}相同,并且您的班级可以兑换至char const*
这与uint32_t
转换为char*
的匹配程度相同。
我在这里提供的是一种方法,可以在遇到这种问题时产生“非常有吸引力的超载”,即使您认为应该超载,也不会调用过载。
所以这里有一个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
正文中的用户定义转换,所以在检查匹配时不会发生用户定义的转换,因此这比其他需要用户定义的转换的操作更受欢迎(如{ {1}})。实际上,这是一个“更有吸引力”的重载,而不是任何与参数完全不匹配的重载。
这可能会导致问题,如果您有另一个需要operator[]
的重载,并且uint32_t[char*]
转换为const Bar&
,则上述重载可能会让您感到惊讶并捕获传入的内容Bar
- rvalues和非const变量都比std::string
更符合上述Bar
签名!