2.寻找可行函数集合(Viable Function Set):这一步的判断条件是函数参数是否匹配。
"匹配"指调用函数使用的参数是否完全符合或者经过转换后完全符合函数的定义的参数。
a) exact match: 有下面四种形式
1)Lvalue-to-rvalue
rvalue-to-Lvalue:特别注意不要与reference initialize混淆。
reference initialize:用其它lvalue来初始化引用。
但不能用rvalue来初始化。
const reference initialize:用rvalue来初始化const引用。
如果用lvalue来初始化const引用,则实际上是:先初始化为引用,再加上const修饰
const lvalue只能用来初始化const引用,不能用来初始化引用。
引用只要在初始化后才是一个lvalue。一旦初始化,它的使用将与一个lvalue的变量无异。
lvalue:比如一般定义的变量,引用等;
rvalue:主要有下面的4种情形:
int i = 3; //1: 常量3
double d = (double)i; //2: 转换的中间值
long f(int);
f(i); //3: i是lvalue, 它会先转换成一个rvalue再传给函数,即有个中间值
long id = f(i); //4: 函数返回值为一个rvalue
例子:
long f(int) { return 0L; }
long g(int &) { return 0L; }
long h(int const &) { return 0L; }
int main()
{
int i = 1; //rvalue-to-Lvalue
f(i); //参数:lvalue-to-rvalue
f(2); //?这个和上面的f(i)的调用一样需要什么转换吗?
g(i); //reference initialize
//g(2); //error.没法进行reference initialize
h(i); //const reference initialize
h(2); //const reference initialize
}
特别注意:这4个转换很饶人,如果把它们作为重载的材料,则不要过分使用它们,推荐:
1. void f(T)单独定义
2. void f(T&)和void f(T const&)联合使用。
这两个函数不要与void f(T)同时出现,不然很容易导致“二义调用”。
而这两个函数是否都定义取决于你想怎么调用它们:如果用const值和rvalue作为参数调用f,则要定义f(T const &)
如果想改变参数,则要定义f(T&)
如果调用的参数要经过standard/user-defined conversion转换为类型T,则也要定义f(T const &),因为转换后的中间结果为一个rvalue
建议内建对象(包括string类)用f(T),类对象用f(T&) 和 f(T const &)
例如:
void f(int) { cout << "f(int)" << endl; }
void g(Matrix) { cout << "g(Matrix)" << endl; }
void g(Matrix const &) { cout << "g(Matrix const &)" << endl; }
int main()
{
int i = 0;
Matrix m;
f(i); //输出:f(int)
g(m); //输出:g(Matrix)
f(1); //输出:f(int)
Matrix const cm;
g(const_rm); //输出g(Matrix const &)
}
很清晰,很明白。
2)array-to-pointer: 数组变量转为指针变量
3)function-to-pointer: 函数名转为函数指针变量
4)qualification: T * -> T const *, T & -> T const &, 本转换只对指针/引用有效。
上面4种转换多数情况是不可逆的:1)rvalue-to-lvalue是可以的。但rvalue不能用来初始化引用,不过能够初始化const引用(为什么能够呢?)。
比如
int i = 3; //ok. 3 is rvalue, i is lvalue
int &ri = 3; //error.
int const &ri2 = 3; //ok
int f(int);
int &ri3 = f(3); //error
int const &ri4 = f(4); //ok
2)函数参数没有“数组”类型
3)函数参数没有function类型
4)T const * -> T*, T const & -> T &都是不容许的。
exact match也是一种转换,比exact match更高的是no conversion(比如T->T const, T->T&).exact match可作为重载的材料,而no conversion则不能,会造成重复定义的编译错。
b) promoting conversion: "数值精度提升"转换,主要有:
它与内建类型的长度有关,参考VC上的长度:
sizeof(bool):1
sizeof(short):2 sizeof(unsigned short):2
sizeof(int):4 sizeof(unsigned int):4
sizeof(long):4 sizeof(unsigned long):4
sizeof(float):4 sizeof(double):8
1. bool/char/unsigned char/short -> int
unsigned short -> int ,这个要求sizeof(unsigned short) < sizeof(int),否则转换为unsinged int
2. float -> double
3. enum -> 可以表示它的最小的整形类型(int/unsigned int/long/unsigned long)
注意没有int -> long, char -> short这些提升。
c) standard conversion:标准转换,可能失去精度,主要有:
1. 内建数值类型之间的转换(promotion conversion中的除开):
2. 两种指针转换:整形数值0 -> 指针
指针 -> void *
3. 到bool值得转换:内建数值类型 -> bool
enum -> bool
指针 -> bool
注意:没有class -> bool的转换。
对float/double -> bool值转换的测试:
if (float(0)) cout << "float(0) -> true" << endl;
if (float(0.0)) cout << "float(0.0) -> true" << endl;
if (float(0.0) == 0) cout << "float(0.0) == 0" << endl;
if (float(0.0) == double(0)) cout << "float(0.0) == double(0)" << endl;
if (double(0)) cout << "double(0) -> true" << endl;
if (0.0) cout << "0.0 -> true" << endl; //注意数值0.0的类型为double
if (0.0 == 0) cout << "0.0 == 0" << endl;
if (double(0.0) == float(0)) cout << "double(0.0) == float(0)" << endl;
输出: float(0.0) == 0
float(0.0) == double(0)
0.0 == 0
double(0.0) == float(0)
显然float和double类型的值转换为bool时都是为false,即使其值为0
所以要用'==0'来判断float/double值是否为零。
(不知道是不是标准?还是VC自己的扩展?)
d)user-defined conversion: 自定义类型T与其它类型的转换,主要有:
1. 通过T的构造函数将其它类型转为T:
class S;
class T
{
public: T(S &s){}; //用于将S转换为T
};
这里的S可以为int等内建类型。
2. 通过其它类的user-defined conversion将其它类转换为T:
class T; //在1.中定义
class S
{
public: operater T() { return T(*this); } //用于将S转换为T
};
注意:如果既可以通过1也可以通过2将类型S转换为类型T,则可能出现“不明确的用户定义的转换”:
void f(T t);
S s;
f(s); //error.“不明确的用户定义的转换”
//这样却可以:f( S() );
//原因是S()的返回值为一个rvalue, 不能用它调用T(S&)
上面四种转换可以进行多次,成为一个转换序列:
1. Standard Conversion Sequence: 典型地为:
lvalue Transformation ->
Promotion Conversion or Standard Conversion ->
Qualifications Conversion
2. User-defined Conversion Sequence:
Standard Conversion Sequence ->
User-defined Conversion ->
Standard Conversion Sequence
其中:User-defined Conversion只能进行一次。
总结:对候选集合中的一个函数,
如果存在一条转换序列将调用时使用的参数转换为函数定义的参数类型,则它是一个“可行函数”,加入“可行集合”;
如果没有这样的一条转换序列,则忽略这个候选函数(并不会报错)
如果分析完所有候选集合后,可行集合仍然为空,则报编译错,典型的为“none of the number1 overloads can convert parameter number2 from type 'type'”
中文:" 2 个重载中没有一个可以转换参数 1(从“int”类型)"
调用函数时使用显式的类型转换可以减少转换的步骤,因为显式的转换是不算在conversion sequence中的。
"匹配"指调用函数使用的参数是否完全符合或者经过转换后完全符合函数的定义的参数。
a) exact match: 有下面四种形式
1)Lvalue-to-rvalue
rvalue-to-Lvalue:特别注意不要与reference initialize混淆。
reference initialize:用其它lvalue来初始化引用。
但不能用rvalue来初始化。
const reference initialize:用rvalue来初始化const引用。
如果用lvalue来初始化const引用,则实际上是:先初始化为引用,再加上const修饰
const lvalue只能用来初始化const引用,不能用来初始化引用。
引用只要在初始化后才是一个lvalue。一旦初始化,它的使用将与一个lvalue的变量无异。
lvalue:比如一般定义的变量,引用等;
rvalue:主要有下面的4种情形:
int i = 3; //1: 常量3
double d = (double)i; //2: 转换的中间值
long f(int);
f(i); //3: i是lvalue, 它会先转换成一个rvalue再传给函数,即有个中间值
long id = f(i); //4: 函数返回值为一个rvalue
例子:
long f(int) { return 0L; }
long g(int &) { return 0L; }
long h(int const &) { return 0L; }
int main()
{
int i = 1; //rvalue-to-Lvalue
f(i); //参数:lvalue-to-rvalue
f(2); //?这个和上面的f(i)的调用一样需要什么转换吗?
g(i); //reference initialize
//g(2); //error.没法进行reference initialize
h(i); //const reference initialize
h(2); //const reference initialize
}
特别注意:这4个转换很饶人,如果把它们作为重载的材料,则不要过分使用它们,推荐:
1. void f(T)单独定义
2. void f(T&)和void f(T const&)联合使用。
这两个函数不要与void f(T)同时出现,不然很容易导致“二义调用”。
而这两个函数是否都定义取决于你想怎么调用它们:如果用const值和rvalue作为参数调用f,则要定义f(T const &)
如果想改变参数,则要定义f(T&)
如果调用的参数要经过standard/user-defined conversion转换为类型T,则也要定义f(T const &),因为转换后的中间结果为一个rvalue
建议内建对象(包括string类)用f(T),类对象用f(T&) 和 f(T const &)
例如:
void f(int) { cout << "f(int)" << endl; }
void g(Matrix) { cout << "g(Matrix)" << endl; }
void g(Matrix const &) { cout << "g(Matrix const &)" << endl; }
int main()
{
int i = 0;
Matrix m;
f(i); //输出:f(int)
g(m); //输出:g(Matrix)
f(1); //输出:f(int)
Matrix const cm;
g(const_rm); //输出g(Matrix const &)
}
很清晰,很明白。
2)array-to-pointer: 数组变量转为指针变量
3)function-to-pointer: 函数名转为函数指针变量
4)qualification: T * -> T const *, T & -> T const &, 本转换只对指针/引用有效。
上面4种转换多数情况是不可逆的:1)rvalue-to-lvalue是可以的。但rvalue不能用来初始化引用,不过能够初始化const引用(为什么能够呢?)。
比如
int i = 3; //ok. 3 is rvalue, i is lvalue
int &ri = 3; //error.
int const &ri2 = 3; //ok
int f(int);
int &ri3 = f(3); //error
int const &ri4 = f(4); //ok
2)函数参数没有“数组”类型
3)函数参数没有function类型
4)T const * -> T*, T const & -> T &都是不容许的。
exact match也是一种转换,比exact match更高的是no conversion(比如T->T const, T->T&).exact match可作为重载的材料,而no conversion则不能,会造成重复定义的编译错。
b) promoting conversion: "数值精度提升"转换,主要有:
它与内建类型的长度有关,参考VC上的长度:
sizeof(bool):1
sizeof(short):2 sizeof(unsigned short):2
sizeof(int):4 sizeof(unsigned int):4
sizeof(long):4 sizeof(unsigned long):4
sizeof(float):4 sizeof(double):8
1. bool/char/unsigned char/short -> int
unsigned short -> int ,这个要求sizeof(unsigned short) < sizeof(int),否则转换为unsinged int
2. float -> double
3. enum -> 可以表示它的最小的整形类型(int/unsigned int/long/unsigned long)
注意没有int -> long, char -> short这些提升。
c) standard conversion:标准转换,可能失去精度,主要有:
1. 内建数值类型之间的转换(promotion conversion中的除开):
2. 两种指针转换:整形数值0 -> 指针
指针 -> void *
3. 到bool值得转换:内建数值类型 -> bool
enum -> bool
指针 -> bool
注意:没有class -> bool的转换。
对float/double -> bool值转换的测试:
if (float(0)) cout << "float(0) -> true" << endl;
if (float(0.0)) cout << "float(0.0) -> true" << endl;
if (float(0.0) == 0) cout << "float(0.0) == 0" << endl;
if (float(0.0) == double(0)) cout << "float(0.0) == double(0)" << endl;
if (double(0)) cout << "double(0) -> true" << endl;
if (0.0) cout << "0.0 -> true" << endl; //注意数值0.0的类型为double
if (0.0 == 0) cout << "0.0 == 0" << endl;
if (double(0.0) == float(0)) cout << "double(0.0) == float(0)" << endl;
输出: float(0.0) == 0
float(0.0) == double(0)
0.0 == 0
double(0.0) == float(0)
显然float和double类型的值转换为bool时都是为false,即使其值为0
所以要用'==0'来判断float/double值是否为零。
(不知道是不是标准?还是VC自己的扩展?)
d)user-defined conversion: 自定义类型T与其它类型的转换,主要有:
1. 通过T的构造函数将其它类型转为T:
class S;
class T
{
public: T(S &s){}; //用于将S转换为T
};
这里的S可以为int等内建类型。
2. 通过其它类的user-defined conversion将其它类转换为T:
class T; //在1.中定义
class S
{
public: operater T() { return T(*this); } //用于将S转换为T
};
注意:如果既可以通过1也可以通过2将类型S转换为类型T,则可能出现“不明确的用户定义的转换”:
void f(T t);
S s;
f(s); //error.“不明确的用户定义的转换”
//这样却可以:f( S() );
//原因是S()的返回值为一个rvalue, 不能用它调用T(S&)
上面四种转换可以进行多次,成为一个转换序列:
1. Standard Conversion Sequence: 典型地为:
lvalue Transformation ->
Promotion Conversion or Standard Conversion ->
Qualifications Conversion
2. User-defined Conversion Sequence:
Standard Conversion Sequence ->
User-defined Conversion ->
Standard Conversion Sequence
其中:User-defined Conversion只能进行一次。
总结:对候选集合中的一个函数,
如果存在一条转换序列将调用时使用的参数转换为函数定义的参数类型,则它是一个“可行函数”,加入“可行集合”;
如果没有这样的一条转换序列,则忽略这个候选函数(并不会报错)
如果分析完所有候选集合后,可行集合仍然为空,则报编译错,典型的为“none of the number1 overloads can convert parameter number2 from type 'type'”
中文:" 2 个重载中没有一个可以转换参数 1(从“int”类型)"
调用函数时使用显式的类型转换可以减少转换的步骤,因为显式的转换是不算在conversion sequence中的。