代码之家  ›  专栏  ›  技术社区  ›  Kirill V. Lyadvinsky

为什么这里有歧义?

  •  33
  • Kirill V. Lyadvinsky  · 技术社区  · 16 年前

    假设我有以下最小代码:

    #include <boost/type_traits.hpp>
    
    template<typename ptr_t>
    struct TData
    {
        typedef typename boost::remove_extent<ptr_t>::type value_type;
        ptr_t data;
    
        value_type & operator [] ( size_t id ) { return data[id]; }
        operator ptr_t & () { return data; }
    };
    
    int main( int argc, char ** argv )
    {
        TData<float[100][100]> t;   
        t[1][1] = 5;
        return 0;
    }
    

    GNUC++给了我错误:

    test.cpp: In function 'int main(int, char**)':
    test.cpp:16: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for second:
    test.cpp:9: note: candidate 1: typename boost::remove_extent<ptr_t>::type& TData<ptr_t>::operator[](size_t) [with ptr_t = float [100][100]]
    test.cpp:16: note: candidate 2: operator[](float (*)[100], int) <built-in>
    

    我的问题是:

    1. 为什么GNU C++给出了错误,但是英特尔C++编译器不是吗?
    2. 为什么要改变 operator[] 以下是编译时不会出错的原因?

      value_type & operator [] ( int id ) { return data[id]; }

    欢迎链接到C++标准。


    如我所见,这里有两个转换路径:

    1. (1) int 到 size_t (2) operator[](size_t) .
    2. (1) operator ptr_t&() ,(2) int 到 西泽特 (3)内置 运算符[](大小) .
    6 回复  |  直到 13 年前
        1
  •  31
  •   Johannes Schaub - litb    16 年前

    它实际上是相当直的。为了 t[1] ,重载解决方案具有以下候选项:

    备选方案1(内置:13.6/13) (T是任意对象类型):

    • 参数列表: (T*, ptrdiff_t)

    候选人2(你的接线员)

    • 参数列表: (TData<float[100][100]>&, something unsigned)

    参数列表由 13.3.1.2/6 :

    过载解决的候选函数集是成员候选、非成员候选和内置候选的联合。参数列表包含运算符的所有操作数。

    • 参数列表: (TData<float[100][100]>, int)

    您会看到第一个参数与候选2的第一个参数完全匹配。但它需要用户为候选1的第一个参数定义转换。所以对于第一个参数,第二个候选人获胜。

    你也可以看到第二个位置的结果取决于。让我们做一些假设,看看我们得到了什么:

    1. ptrdiff_t 是 int :第一个候选人获胜,因为它有一个精确的匹配,而第二个候选人需要积分转换。
    2. pTrdFifft 是 long :两个候选人都没有获胜,因为两者都需要积分转换。

    现在, 13.3.3/1 说

    让icsi(f)表示将列表中的第i个参数转换为可行函数f的第i个参数类型的隐式转换序列。

    一个可行函数f1被定义为比另一个可行函数f1更好的函数,如果对于所有参数i,icsi(f1)不是比icsi(f2)更差的转换序列,那么…对于某些参数j,icsj(f1)是比icsj(f2)更好的转换序列,或者,如果不是的话…

    对于我们的第一个假设,我们不会得到一个整体的胜利者,因为候选2会赢得第一个参数,候选1会赢得第二个参数。我称之为 纵横交错 . 对于我们的第二个假设,候选2总体上胜出,因为两个参数的转换都不差,但第一个参数的 更好的 转换。

    对于第一个假设,第二个参数中的积分转换(int到unsigned)与第一个参数中其他候选项的用户定义转换相比,没有什么问题。纵横交错,规则很粗糙。


    最后一点可能还是会让你困惑,因为周围的人都在大惊小怪,所以让我们举个例子。

    void f(int, int) { }
    void f(long, char) { }
    
    int main() { f(0, 'a'); }
    

    这给了你同样令人困惑的GCC警告(我记得,这实际上是混淆了我的地狱当我几年前第一次收到它),因为 0 转换为 长的 比 'a' 到 int -然而,你会得到一个模棱两可的结果,因为你正处于一个纵横交错的境地。

        2
  •  13
  •   Sarfaraz Nawaz    13 年前

    使用表达式:

    t[1][1] = 5;
    

    编译器必须将焦点放在左侧以确定要执行的操作,因此 = 5; 被忽略,直到解决lhs。留给我们的是这样的表达: t[1][1] ,表示两个操作,第二个操作是对第一个操作的结果进行的,因此编译器必须只考虑表达式的第一部分: t[1] 。实际类型为 (TData&)[(int)]

    调用不完全匹配任何函数,因为 operator[] 对于 TData 被定义为 size_t 参数,为了能够使用它,编译器必须转换 1 从 int 到 西泽特 隐式转换。这是第一选择。现在,另一个可能的路径是应用用户定义的转换来转换 TData<float[100][100]> 进入之内 float[100][100] .

    这个 int 到 西泽特 转换是一个 积分转换 排名为 转换 在本标准表9中, 用户定义的转换 从 t数据浮动[100][100]gt; 到 浮点〔100〕〔100〕 根据第13.3.3.1.2/4节进行转换。转换自 float [100][100]& 到 float (*)[100] 被列为 精确匹配 表9。不允许编译器从这两个转换序列中进行选择。

    Q1 :并非所有编译器都以相同的方式遵守标准。发现在某些特定情况下编译器的执行方式与其他编译器的执行方式不同是很常见的。在这种情况下,G++实现人员决定抱怨标准不允许编译器选择,而Intel实现人员可能只是悄悄地应用了他们喜欢的转换。

    Q2 :更改用户定义的签名时 操作员[ ] ,参数与传入的类型完全匹配。 T〔1〕 非常适合 t.operator[](1) 没有任何转换,因此编译器必须遵循该路径。

        3
  •  0
  •   adf88    16 年前

    我不知道确切的答案是什么,但是…

    因为这个操作员:

    operator ptr_t & () { return data; }
    

    已经有内置的 [] 接受的运算符(数组订阅) size_t 作为索引。所以我们有两个 [] 操作符,内置的和由您定义的。布斯接受 西泽特 所以这可能被认为是非法超载。

    //编辑
    这应该是你想要的

    template<typename ptr_t>
    struct TData
    {
        ptr_t data;
        operator ptr_t & () { return data; }
    };
    
        4
  •  0
  •   UncleBens    16 年前

    在我看来

    t[1][1] = 5;
    

    编译器必须在两者之间进行选择。

    value_type & operator [] ( size_t id ) { return data[id]; }
    

    如果 int 文本将转换为 size_t 或

    operator ptr_t & () { return data; }
    

    然后是普通数组索引,在这种情况下,索引的类型完全匹配。


    至于这个错误,作为编译器扩展的gcc似乎想为您选择第一个重载,而您正在使用-pedantic和/或-werror标志进行编译,这迫使它坚持标准的说法。

    (我没有学究的心情,所以没有引用标准中的任何一句话,尤其是关于这个主题的话。)

        5
  •  0
  •   Chubsdad    16 年前

    我试着向两位候选人展示T[1][1]这个词。这两个等级(转换)相等。因此歧义

    我认为这里要注意的是,根据13.6/13的内置[]运算符定义为

    T& operator[](T*, ptrdiff_t);
    

    在我的系统上,ptrdiff_t被定义为“int”(这是否解释了x64行为?)

    template<typename ptr_t> 
    struct TData 
    { 
        typedef typename boost::remove_extent<ptr_t>::type value_type; 
        ptr_t data; 
    
        value_type & operator [] ( size_t id ) { return data[id]; } 
        operator ptr_t & () { return data; } 
    }; 
    
    typedef float (&ATYPE) [100][100];
    
    int main( int argc, char ** argv ) 
    { 
        TData<float[100][100]> t;    
    
        t[size_t(1)][size_t(1)] = 5; // note the cast. This works now. No ambiguity as operator[] is preferred over built-in operator
    
        t[1][1] = 5;                 // error, as per the logic given below for Candidate 1 and Candidate 2
    
        // Candidate 1 (CONVERSION rank)
        // User defined conversion from 'TData' to float array
        (t.operator[](1))[1] = 5;
    
        // Candidate 2 (CONVERSION rank)
        // User defined conversion from 'TData' to ATYPE
        (t.operator ATYPE())[1][1] = 6;
    
        return 0; 
    }
    

    编辑:

    以下是我的想法:

    对于候选1(运算符[]),转换序列s1是 用户定义的转换-标准转换(int到size_t)

    对于候选2,转换序列s2是 用户定义的转换->int到ptrdiff_t(对于第一个参数)->int到ptrdiff_t(对于第二个参数)

    转换序列s1是s2的子集,应该更好。但这是个陷阱…

    下面引用的标准应该会有所帮助。

    $13.3.3.2/3州-标准 转换序列s1更好 转换顺序比标准 如果s1是a,则转换序列s2 s2的适当子序列(比较 中的转换序列 由13.3.3.1.1定义的规范形式, 不包括任何左值转换; 身份转换序列为 被认为是任何 非身份转换序列)或, 如果不是…

    $13.3.3.2状态-“用户定义 转换序列U1更好 转换顺序 用户定义的转换序列u2 if 它们包含相同的用户定义 转换函数或构造函数和 如果第二个标准转换 U1的序列比 第二标准转换序列 U2。”

    这里是和条件的第一部分” 如果它们包含相同的用户定义的转换函数或构造函数 “不行。所以,即使和条件的第二部分 如果U1的第二个标准转换序列优于U2的第二个标准转换序列。 “很好,s1和s2都不是首选。

    这就是为什么海湾合作委员会 幻影 错误消息“ISO C++表示这些是模棱两可的,即使第一个最坏的转换比第二个最坏的转换更好”。

    这就很好地解释了模糊性。

        6
  •  0
  •   Potatoswatter    16 年前

    过载解决是一个头痛的问题。但由于您偶然发现了一个修复(消除了将索引操作数转换为 operator[] )这对于示例来说太特殊了(文本是类型 int 但是你将要使用的大多数变量都不是),也许你可以概括它:

    template< typename IT>
    typename boost::enable_if< typename boost::is_integral< IT >::type, value_type & >::type
    operator [] ( IT id ) { return data[id]; }
    

    不幸的是,我不能测试这个,因为GCC 4.2.1和4.5接受您的示例,而没有根据 --pedantic . 这真的提出了一个问题,它是否是一个编译器错误。

    而且,一旦我消除了对提振的依赖,它就通过了Comeau。