“运算符[]'的模糊重载”如果存在转换运算符到int

时间:2013-04-06 12:17:13

标签: c++

我试图像类的[]运算符一样实现类似的向量。但我收到编译器的错误消息(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}'

3 个答案:

答案 0 :(得分:20)

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

  1. 从字符串文字构造std::string并调用您的重载接受std::string;
  2. Foo对象转换为uint32_t,并将其用作字符串文字的索引。
  3. 虽然选项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"]可以解析为:

  1. "foo"转换为std::string(是s)并f[s]拨打Foo::operator[](const std::string&)
  2. f转换为整数调用Foo::operator int()(是i)并使用内置i["foo"]运算符可交换的众所周知的事实[]
  3. 两者都有一个自定义类型转换,因此含糊不清。

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

    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签名!