为什么指针衰减优先于推导的模板?
Why does pointer decay take priority over a deduced template?
假设我正在编写一个函数来打印字符串的长度:
template <size_t N>
void foo(const char (&s)[N]) {
std::cout << "array, size=" << N-1 << std::endl;
}
foo("hello") // prints array, size=5
现在我想扩展 foo
以支持 non-arrays:
void foo(const char* s) {
std::cout << "raw, size=" << strlen(s) << std::endl;
}
但事实证明这打破了我最初的预期用途:
foo("hello") // now prints raw, size=5
为什么?这不需要数组到指针的转换,而模板是完全匹配的吗?有没有办法确保我的数组函数被调用?
这种(符合标准的)歧义的根本原因似乎在于转换成本:重载解析试图最小化将参数转换为相应参数所执行的操作。数组 实际上 指向其第一个元素的指针,并装饰有一些编译时类型信息。数组到指针的转换成本不会超过例如保存数组本身的地址,或初始化对它的引用。从这个角度来看,这种歧义似乎是合理的,尽管从概念上讲它是不直观的(并且可能低于标准)。事实上,这个论点适用于所有左值转换,正如下面引述所暗示的那样。另一个例子:
void g() {}
void f(void(*)()) {}
void f(void(&)()) {}
int main() {
f(g); // Ambiguous
}
以下为必修标准语。不是某些函数模板特化的函数优于那些如果两者在其他方面同样匹配的函数(参见 [over.match.best]/(1.3), (1.6))。在我们的例子中,执行的转换是数组到指针的转换,它是具有精确匹配等级的左值转换(根据 [over.ics.user] 中的 table 12)。 [over.ics.rank]/3:
Standard conversion sequence S1
is a better conversion sequence than standard conversion sequence S2
if
S1
is a proper subsequence of S2
(comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding
any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion
sequence) or, if not that,
the rank of S1
is better than the rank of S2
, or S1
and S2
have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,
[..]
第一个要点排除了我们的转换(因为它是左值转换)。第二个需要排名差异,但不存在,因为两个转换都具有完全匹配排名; "rules in the paragraph below",即在 [over.ics.rank]/4 中,也不涵盖数组到指针的转换。
所以信不信由你,两个转换序列的 none 比另一个更好,因此选择了 char const*
-重载。
可能的解决方法:将第二个重载也定义为函数模板,然后部分排序开始并选择第一个。
template <typename T>
auto foo(T s)
-> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
std::cout << "raw, size=" << std::strlen(s) << std::endl;
}
Demo.
假设我正在编写一个函数来打印字符串的长度:
template <size_t N>
void foo(const char (&s)[N]) {
std::cout << "array, size=" << N-1 << std::endl;
}
foo("hello") // prints array, size=5
现在我想扩展 foo
以支持 non-arrays:
void foo(const char* s) {
std::cout << "raw, size=" << strlen(s) << std::endl;
}
但事实证明这打破了我最初的预期用途:
foo("hello") // now prints raw, size=5
为什么?这不需要数组到指针的转换,而模板是完全匹配的吗?有没有办法确保我的数组函数被调用?
这种(符合标准的)歧义的根本原因似乎在于转换成本:重载解析试图最小化将参数转换为相应参数所执行的操作。数组 实际上 指向其第一个元素的指针,并装饰有一些编译时类型信息。数组到指针的转换成本不会超过例如保存数组本身的地址,或初始化对它的引用。从这个角度来看,这种歧义似乎是合理的,尽管从概念上讲它是不直观的(并且可能低于标准)。事实上,这个论点适用于所有左值转换,正如下面引述所暗示的那样。另一个例子:
void g() {}
void f(void(*)()) {}
void f(void(&)()) {}
int main() {
f(g); // Ambiguous
}
以下为必修标准语。不是某些函数模板特化的函数优于那些如果两者在其他方面同样匹配的函数(参见 [over.match.best]/(1.3), (1.6))。在我们的例子中,执行的转换是数组到指针的转换,它是具有精确匹配等级的左值转换(根据 [over.ics.user] 中的 table 12)。 [over.ics.rank]/3:
Standard conversion sequence
S1
is a better conversion sequence than standard conversion sequenceS2
if
S1
is a proper subsequence ofS2
(comparing the conversion sequences in the canonical form defined by 13.3.3.1.1, excluding any Lvalue Transformation; the identity conversion sequence is considered to be a subsequence of any non-identity conversion sequence) or, if not that,the rank of
S1
is better than the rank ofS2
, orS1
andS2
have the same rank and are distinguishable by the rules in the paragraph below, or, if not that,[..]
第一个要点排除了我们的转换(因为它是左值转换)。第二个需要排名差异,但不存在,因为两个转换都具有完全匹配排名; "rules in the paragraph below",即在 [over.ics.rank]/4 中,也不涵盖数组到指针的转换。
所以信不信由你,两个转换序列的 none 比另一个更好,因此选择了 char const*
-重载。
可能的解决方法:将第二个重载也定义为函数模板,然后部分排序开始并选择第一个。
template <typename T>
auto foo(T s)
-> std::enable_if_t<std::is_convertible<T, char const*>{}>
{
std::cout << "raw, size=" << std::strlen(s) << std::endl;
}
Demo.