重载的非类型模板不明确,而非模板函数是可以的
Overloaded non-type template is ambiguous while non-templated function is ok
如果我们有一个模板函数,它接受类型为 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),为了明确调用非模板 foo
的 short
版本,我们可以显式实例化一个 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
的调用仍然不明确? (请注意,无模板 foo
工作正常 )。
谢谢。
这里的模板函数是按值参数化的,而且只按值,不按类型! 更新:现在我不确定。
另一方面,非模板化版本是按类型参数化的(并且可以享受多态调用)。
更新:
好吧,看起来实例化函数的名称实际上取决于数字模板参数的类型。
编译器错误消息具有误导性*。你会直观地认为这意味着"The function invocation is ambigious!",但实际上编译在更早的阶段就失败了,那时甚至没有生成专用函数的定义。
真正的意思是:"The function specialization is ambigious!"
让我们看看它是如何编译的:
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 步:编译器认为函数实际被调用(在这种情况下这似乎很明显,但是当你有一个 class 模板时就不那么明显了.),并生成一个定义:
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}>
of foo<short{0}>
,编译器都会为两者生成template<> void foo<0>();
。显式类型将被忽略。 (这就是为什么它们是 constexpr
-s 真的很重要。)
更新
如T.C。在他的评论中指出,在 standard(PDF 的第 413 页)中有一个非常相似的例子:
[Example: In the following example, assuming a signed char cannot
represent the value 1000, a narrowing conversion (8.5.4) would be
required to convert the template-argument of type int to signed char,
therefore substitution fails for the second template (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.
下面是当你写 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
,并且该转换在此上下文中是允许的转换。
替换后,产生了两个候选函数特化。两者都是可行的。然后执行重载解析 - 由于签名相同并且没有应用决胜局,所以重载解析失败并且调用不明确。
这里的要点是正常的重载解析规则不适用于模板参数。模板参数的转换在较早阶段应用,在常规重载解析发生之前。
如果我们有一个模板函数,它接受类型为 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),为了明确调用非模板 foo
的 short
版本,我们可以显式实例化一个 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
的调用仍然不明确? (请注意,无模板foo
工作正常 )。
谢谢。
这里的模板函数是按值参数化的,而且只按值,不按类型! 更新:现在我不确定。 另一方面,非模板化版本是按类型参数化的(并且可以享受多态调用)。
更新: 好吧,看起来实例化函数的名称实际上取决于数字模板参数的类型。
编译器错误消息具有误导性*。你会直观地认为这意味着"The function invocation is ambigious!",但实际上编译在更早的阶段就失败了,那时甚至没有生成专用函数的定义。
真正的意思是:"The function specialization is ambigious!"
让我们看看它是如何编译的:
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 步:编译器认为函数实际被调用(在这种情况下这似乎很明显,但是当你有一个 class 模板时就不那么明显了.),并生成一个定义:
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}>
of foo<short{0}>
,编译器都会为两者生成template<> void foo<0>();
。显式类型将被忽略。 (这就是为什么它们是 constexpr
-s 真的很重要。)
更新 如T.C。在他的评论中指出,在 standard(PDF 的第 413 页)中有一个非常相似的例子:
[Example: In the following example, assuming a signed char cannot represent the value 1000, a narrowing conversion (8.5.4) would be required to convert the template-argument of type int to signed char, therefore substitution fails for the second template (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.
下面是当你写 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
,并且该转换在此上下文中是允许的转换。替换后,产生了两个候选函数特化。两者都是可行的。然后执行重载解析 - 由于签名相同并且没有应用决胜局,所以重载解析失败并且调用不明确。
这里的要点是正常的重载解析规则不适用于模板参数。模板参数的转换在较早阶段应用,在常规重载解析发生之前。