考虑我有以下最小代码:
#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>
我的问题是:
operator[]
更改为以下内容会导致编译没有错误?
value_type & operator [] ( int id ) { return data[id]; }
赞赏C ++标准的链接。
我在这里看到两条转换路径:
int
至size_t
和(2)operator[](size_t)
。operator ptr_t&()
,(2)int
至size_t
和(3)内置operator[](size_t)
。答案 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的第一个参数。因此对于第一个参数,第二个候选获胜。
你也看到第二个位置的结果取决于。让我们做一些假设,看看我们得到了什么:
ptrdiff_t
是int
:第一个候选人获胜,因为它具有完全匹配,而第二个候选人需要进行积分转换。 ptrdiff_t
是long
:两位候选人都没有获胜,因为两者都需要进行积分转换。 现在,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_t
到1
进行隐式转换。这是第一选择。现在,另一种可能的路径是应用用户定义的转换将int
转换为size_t
。
TData<float[100][100]>
到float[100][100]
转化为积分转换,在表9中排名为 Conversion 标准,以及根据§13.3.3.1.2/ 4从 int
到size_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。