如果我们有一个模板函数,它接受类型为int
或short
的非类型参数,则编译器会抱怨以下调用的模糊性:
// Definition
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
// Usage
foo<0>(); // Ambiguous, int or short?
起初我对此行为并不感到惊讶,文字0
可能是int
或short
,但如果我们尝试这样做:
// Definition
void foo(int i) { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }
// Usage
foo(0); // "i: 0"!
对foo
的调用毫不含糊!它需要int
重载(即使模板版本没有)。好了,经过一番思考后,这并不是一个令人惊讶的行为,毕竟没有办法指定short
字面值,所以编译器认为0
是int
(这是默认行为AFAIK),为了明确地调用非模板short
的{{1}}版本,我们可以显式实例化foo
:
short
所以我认为这会明确模板化版本,但它没有:
foo(0); // "i: 0"
foo(short{0}); // "s: 0"
foo<int{0}>(); // Ambiguous call, candidates are int and short versions foo<short{0}>(); // Ambiguous call, candidates are int and short versions
我尝试的最后一件事是使用实例而不是文字:
call of overloaded 'foo()' is ambiguous
foo<int{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]
call of overloaded 'foo()' is ambiguous
foo<short{0}>();
note: candidates are:
void foo() [with int I = 0]
void foo() [with short int S = 0]
没有成功,你可以......所以,问题是什么?
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
void foo(int i) { std::cout << "i: " << i << '\n'; }
void foo(short s) { std::cout << "s: " << s << '\n'; }
constexpr int i{1};
constexpr short s{5};
int main()
{
foo(i); // "i: 1"
foo(s); // "s: 5"
foo<i>(); // Ambiguous! (expected "I: 1")
foo<s>(); // Ambiguous! (expected "S: 5")
return 0;
}
的调用含糊不清? (请注意,无模板foo
需要foo
版本,因此是明确的。)int
的调用仍然不明确? (请注意,无模板foo
正常工作)。感谢。
答案 0 :(得分:5)
编译器错误消息是missleading *。你会直觉地认为这意味着“函数调用是一个暧昧的!”,但实际上编译在早期阶段失败了,那时甚至没有生成专用函数的定义。
它的真正含义是:“功能专业化是暧昧的!”
让我们看看这是如何编译的:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
编译的第一步是模板专业化。
第1步:编译器意识到foo<0>
是模板特化,并相应地生成函数声明:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
第2步:编译器重新调用该函数实际被调用(在这种情况下这似乎很明显,但是当你有一个类模板时它就不那么明显了。),并生成一个定义: / p>
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
template<>
void foo<0>(){
std::cout << "S: " << 0 << '\n';
}
第3步:现在你有一个可调用的函数,编译继续正常进行。
让我们尝试按照您的情况执行相同的步骤:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
第1步:生成函数声明:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
foo<0>();
return 0;
}
此时编译失败,因为foo的专门声明是暧昧的。 如果您需要证明,请尝试编译此代码:
template <short S> void foo() { std::cout << "S: " << S << '\n'; }
template <int I> void foo() { std::cout << "I: " << I << '\n'; }
template<>
void foo<0>();
int main(int argc, char* argv[])
{
return 0;
}
如果没有函数调用,您将收到相同的错误消息!
<强>更新强>
所以,重要的是所有转换为专门的函数声明。因此,无论您编写foo<int{0}>
foo<short{0}>
,编译器都会为两者生成template<> void foo<0>();
。显式类型将被忽略。 (这就是为什么它们非常重要constexpr
- s。)
<强>更新强> 作为T.C.在他的评论中指出,在standard(PDF中的第413页)中有一个非常相似的例子:
[示例:在以下示例中,假设签名的char不能 代表值1000,缩小转换率(8.5.4) 需要将int类型的template-argument转换为signed char, 因此第二个模板的替换失败(14.3.2)。
template <int> int f(int); template <signed char> int f(int); int i1 = f<1000>(0); // OK int i2 = f<1>(0); // ambiguous; not narrowing
-end example]
*错误消息完全正确。可能不是特别直观,但它反映了标准中规定的程序。 - T.C。
答案 1 :(得分:5)
在这里写下f<0>()
时会发生什么。
编译器查找f
,找到两个函数模板声明:
template <int I> void foo();
template <short S> void foo();
编译器会看到显式模板参数列表,并尝试将其替换为每个函数模板声明:
template <int I> void foo(); // with I = 0
template <short S> void foo(); // with S = 0
在这两种情况下,替换成功,因为0
是int
,可以转换为short
,转换是在此上下文中允许的转化。
替换后,生成两个候选函数专精。两者都是可行的。然后执行过载分辨率 - 由于签名相同且没有仲裁器适用,因此重载解析失败并且调用不明确。
这里的要点是正常的重载决策规则不适用于模板参数。在常规重载解析发生之前,模板参数的转换将在早期阶段应用。
答案 2 :(得分:0)
此处的模板函数按值进行参数化,并且仅按值而非按类型进行参数化! 更新:现在我不确定。 另一方面,没有模板化版本按类型进行参数化(并且可以享受多态调用)。
<强>更新强> 好吧,看起来像实例化的函数损坏名称实际上取决于数值模板参数的类型。