为什么这里含糊不清?

时间:2010-08-19 06:30:31

标签: c++ templates operator-overloading

考虑我有以下最小代码:

#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;
}

GNU C ++给了我错误:

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]; }

  3. 赞赏C ++标准的链接。


    我在这里看到两条转换路径:

    1. (1)intsize_t和(2)operator[](size_t)
    2. (1)operator ptr_t&(),(2)intsize_t和(3)内置operator[](size_t)

6 个答案:

答案 0 :(得分:31)

实际上非常直接。对于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)

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

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

  1. ptrdiff_tint:第一个候选人获胜,因为它具有完全匹配,而第二个候选人需要进行积分转换。
  2. ptrdiff_tlong:两位候选人都没有获胜,因为两者都需要进行积分转换。
  3. 现在,13.3.3/1

      

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

         

    如果对于所有参数i,可行函数F1被定义为比另一个可行函数F2更好的函数,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转换为long'a'更糟糕int到{{1}} - 但是你得到的含糊不清,因为你处于纵横交错的境地。

答案 1 :(得分:13)

使用表达式:

t[1][1] = 5;

编译器必须关注左侧以确定其中的内容,因此{l}在解析之前将忽略= 5;。让我们使用表达式t[1][1],它代表两个操作,第二个操作对第一个操作的结果进行操作,因此编译器必须只考虑表达式的第一部分:t[1] 。实际类型为(TData&)[(int)]

调用与任何函数都不匹配,因为operator[]的{​​{1}}被定义为采用TData参数,因此为了能够使用它,编译器必须转换{ {1}}从size_t1进行隐式转换。这是第一选择。现在,另一种可能的路径是应用用户定义的转换将int转换为size_t

TData<float[100][100]>float[100][100] 转化为积分转换,在表9中排名为 Conversion 标准,以及根据§13.3.3.1.2/ 4从 intsize_t 转换的用户定义转换。在表9中,从TData<float[100][100]>float[100][100]的转换被排列为完全匹配。不允许编译器从这两个转换序列中进行选择。

Q1 :并非所有编译器都以相同的方式遵守标准。很常见的是,在某些特定情况下,编译器的执行速度与其他编译器不同。在这种情况下,g ++实现者决定抱怨不允许编译器选择的标准,而英特尔实现者可能只是默默地应用他们的首选转换。

Q2 :更改用户定义的float [100][100]&的签名时,参数与传入的类型完全匹配。 float (*)[100]operator[]的完美匹配,没有任何转换,因此编译器必须遵循该路径。

答案 2 :(得分:0)

我试图展示表达式t [1] [1]的两个候选者。这些都是相等的RANK(CONVERSION)。因此含糊不清

我认为这里的问题是内置的[]运算符按照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 to size_t)

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

转换序列S1是S2的子集,应该更好。但这是抓住......

以下引用标准应该有所帮助。

  

$ 13.3.3.2 / 3州 - 标准   转换顺序S1更好   转换顺序比标准   转换序列S2,如果 - S1是a   适当的S2后续序列(比较   转换序列   由13.3.3.1.1定义的规范形式,   排除任何左值变换;   身份转换序列是   被认为是任何一个的后果   非身份转换序列)或,   如果不是那样......

     

$ 13.3.3.2陈述 - “用户定义的   转换序列U1更好   转换顺序比另一个   用户定义的转换序列U2 if   它们包含相同的用户定义   转换函数或构造函数   如果第二次标准转换   U1的序列优于   第二标准转换序列   U2“。

这里和条件“如果它们包含相同的用户定义转换函数或构造函数”的第一部分并不成立。因此,即使条件“如果U1的第二个标准转换序列优于U2的第二个标准转换序列。”的第二部分保持良好,S1和S2都不优于其他

这就是为什么gcc的幻像错误消息“ISO C ++说这些是模糊的,即使第一次转换的最差转换比第二次转换的最差转换”

这解释了暧昧安静的恕我直言

答案 3 :(得分:0)

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

由于此操作符:

operator ptr_t & () { return data; }

已经存在内置[]运算符(数组预订),它接受size_t作为索引。所以我们有两个[]运算符,内置并由您定义。 Booth接受size_t,因此可能认为这是非法过载。

// EDIT
这应该按照你的预期工作

template<typename ptr_t>
struct TData
{
    ptr_t data;
    operator ptr_t & () { return data; }
};

答案 4 :(得分:0)

在我看来,

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)

重载解决方案令人头疼。但是因为你偶然发现了一个修复(消除了将索引操作数转换为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下没有投诉就接受了您的示例。这真的引发了一个问题,即它是否是编译器错误。

此外,一旦我消除了Boost依赖,它就通过了Comeau。