C++ 模板和对重载函数的模糊调用
c++ templates and ambiguous call to overloaded function
我遇到了以下问题。我使用的是 Xcode 7,我的项目没有问题。尝试在 Visual Studio Express 2015 上编译后,我在代码中收到消息 "Error C2668 ambiguous call to overloaded function"
。我找不到与该问题相关的 visual studio 的任何具体信息。
我做了一个应该在 VS 上使用的最小工作示例(在 Xcode 上没有错误)。奇怪的部分涉及 func2
部分。就好像 VS 编译器无法自动推断出比限制更多的类型 and/or 参数。
更新:
Sam Varshavchik 提出了一种解决方法,包括使用带有中间模板的部分专业化 class。这是我想避免的解决方案。首先是因为它在我的代码中应用的上下文中不方便,其次是因为我不清楚这个编译错误。在Xcode7中没有出现这个错误,func2即使在VS中也没有错误。尽管我同意 WhiZTiM 的解释,但事实是,在此上下文中的重载有时有效,有时无效。我真的很想知道为什么。
更新 2:
根据 bogdan 的说法,这可能是 GCC 和 MSVC 中的错误。我会尝试报告它。 (第一周我非常喜欢 visual studio)
更新 3:
错误报告于 https://connect.microsoft.com/VisualStudio/feedback/details/3076577
functions.h :
template <class T>
class BX {
public :
BX() {}
~BX() {}
};
template <class T1, class T2>
class G {
public :
G() {}
~G() {}
};
template <template <class T> class T1, class T>
class DT {};
class B {
public :
//I want func to work
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//here is a small variation of func as a test
template <template <class T> class T1, class T, class M>
static void func2(const M& m, const DT<T1, T>* dt) {}
template <template <class T> class T1, class T, class M>
static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};
main.cpp
int main() {
BX< int > bx;
G<BX< int >, int> g;
DT<BX, int>* dt;
B::func(g, dt, bx);//Error C2668 'B::func': ambiguous call to overloaded function
B::func2(g, dt);//no error
}
你打了这个电话:
B::func(g, dt, bx);
其中:
g
是类型 G<BX< int >, int>
dt
的类型是 DT<BX, int>*
bx
的类型是 BX< int >
现在你有这两个功能:
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
//^^^^^^^^^^
template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//^^^^^^^^^^^^^^^^^^
在重载决议期间;并仅考虑函数声明中的第一个参数(因为据推测这些参数使函数声明不同):
第一个函数重载中的 M
被推断为 G<BX< int >, int>
。
- 第二个函数重载具有应匹配的模板化类型。
T
可以从 bx
推导出为 int
T1<T>
模板模板类型,从 bx
推导为 BX< int >
M
将匹配任何内容。
- 最后,您将第一个参数推导为
G<BX< int >, int>
,与第一个函数 的参数相同
GCC 也会引发歧义错误。
要在传递 G<...>
类型时优先使用第二个重载函数,您需要使用部分特化。 (因为它们的排名高于主要模板)。请参见
一种可能的方法。
所示代码的明显意图是部分函数特化。
哪个...行不通。
那么,怎么办,怎么办...好吧,将部分函数专业化转换为普通模板如何?class 专业化?
我的解决方案在第一个函数参数类型上特化了一个模板,以消除静态 class 的歧义,并将其转发给两个最终 class 方法之一。
一个好的 C++ 编译器应该能够优化掉额外的函数调用层:
template <class T>
class BX {
public :
BX() {}
~BX() {}
};
template <class Tdata, class Tmetric>
class G {
public :
G() {}
~G() {}
};
template <template <class T> class T1, class T>
class DT {};
template<class M> class B_func;
class B {
public :
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val)
{
B_func<M>::func(m, dt, val);
}
template <template <class T> class T1, class T, class M>
static void func_a(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
template <template <class T> class T1, class T, class M>
static void func_b(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//here is a small variation of func as a test
template <template <class T> class T1, class T, class M>
static void func2(const M& m, const DT<T1, T>* dt) {}
template <template <class T> class T1, class T, class M>
static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};
template <class M>
class B_func {
public:
template<class two, class three>
static void func(const M& m, const two* dt, three& val)
{
B::func_a(m, dt, val);
}
};
template <template <class T> class T1, class T, class M>
class B_func<G<T1<T>, M>> {
public:
template<class two, class three>
static void func(const G<T1<T>, M>& m, const two* dt, three& val)
{
B::func_b(m, dt, val);
}
};
int main() {
BX< int > bx;
G<BX< int >, int> g;
DT<BX, int>* dt;
B::func(g, dt, bx);
B::func2(g, dt);
return 0;
}
这看起来像是 MSVC 和 GCC 中的错误。该调用应解析为第二个重载(Clang 和 EDG 正在这样做)。
对于调用 B::func(g, dt, bx)
,名称查找找到两个 func
模板。对它们中的每一个执行模板参数推导和替换,以生成随后可以参与重载决策的函数模板专业化声明。两个模板推导成功,剩下两个特化:
void B::func<BX, int, G<BX<int>, int>>(const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
void B::func<BX, int, int> (const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
这两个函数具有相同的参数声明子句,因此重载决策显然无法根据调用参数的转换来区分它们;它必须求助于过程中的最后两个步骤。
首先,如果其中一个函数是模板特化而另一个不是,则首选非模板函数;此处不适用。
最后,它查看从中合成两个专业化声明的模板;如果根据函数模板的偏序,其中一个模板比另一个更特化,则首选相应的特化。 (这是原始模板重新发挥作用的过程中唯一的地方。)
下面的描述不是很准确,并且跳过了很多细节,但我试图将注意力集中在与本案例相关的部分。非常粗略:
首先,引用和 cv 限定符从两个模板的函数参数声明中剥离,产生:
F1(M , const DT<T1, T>*, T1<T>)
F2(G<T2<U>, V>, const DT<T2, U>*, T2<U>)
(更改了模板参数名称以避免混淆)
然后,尝试推导,就好像使用另一个模板的函数参数形式作为参数调用一个模板,然后反过来。在这种情况下,最后两对对应的参数具有相同的形式,因此两种方式推导都成功。对于第一对对应的参数:
- 从
G<T2<U>, V>
形式的参数中推导出 M
是可行的; M
推导为 G<T2<U>, V>
.
- 从
M
形式的参数中推导 G<T2<U>, V>
中的 T2
、U
和 V
不起作用(M
可以是任何东西)。
换句话说,G<T2<U>, V>
比M
是"more specific"形式;它不能表示 M
可以表示的所有类型;这是 more specialized 试图在这种情况下形式化的直观含义。
因此,推导适用于从 F2
到 F1
的所有对应参数对,但反之则不然。这使得 F2
在部分排序方面比 F1
更专业。
这意味着对应于第二个模板的特化优先于重载决议。
我遇到了以下问题。我使用的是 Xcode 7,我的项目没有问题。尝试在 Visual Studio Express 2015 上编译后,我在代码中收到消息 "Error C2668 ambiguous call to overloaded function"
。我找不到与该问题相关的 visual studio 的任何具体信息。
我做了一个应该在 VS 上使用的最小工作示例(在 Xcode 上没有错误)。奇怪的部分涉及 func2
部分。就好像 VS 编译器无法自动推断出比限制更多的类型 and/or 参数。
更新:
Sam Varshavchik 提出了一种解决方法,包括使用带有中间模板的部分专业化 class。这是我想避免的解决方案。首先是因为它在我的代码中应用的上下文中不方便,其次是因为我不清楚这个编译错误。在Xcode7中没有出现这个错误,func2即使在VS中也没有错误。尽管我同意 WhiZTiM 的解释,但事实是,在此上下文中的重载有时有效,有时无效。我真的很想知道为什么。
更新 2:
根据 bogdan 的说法,这可能是 GCC 和 MSVC 中的错误。我会尝试报告它。 (第一周我非常喜欢 visual studio)
更新 3:
错误报告于 https://connect.microsoft.com/VisualStudio/feedback/details/3076577
functions.h :
template <class T>
class BX {
public :
BX() {}
~BX() {}
};
template <class T1, class T2>
class G {
public :
G() {}
~G() {}
};
template <template <class T> class T1, class T>
class DT {};
class B {
public :
//I want func to work
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//here is a small variation of func as a test
template <template <class T> class T1, class T, class M>
static void func2(const M& m, const DT<T1, T>* dt) {}
template <template <class T> class T1, class T, class M>
static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};
main.cpp
int main() {
BX< int > bx;
G<BX< int >, int> g;
DT<BX, int>* dt;
B::func(g, dt, bx);//Error C2668 'B::func': ambiguous call to overloaded function
B::func2(g, dt);//no error
}
你打了这个电话:
B::func(g, dt, bx);
其中:
g
是类型G<BX< int >, int>
dt
的类型是DT<BX, int>*
bx
的类型是BX< int >
现在你有这两个功能:
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
//^^^^^^^^^^
template <template <class T> class T1, class T, class M>
static void func(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//^^^^^^^^^^^^^^^^^^
在重载决议期间;并仅考虑函数声明中的第一个参数(因为据推测这些参数使函数声明不同):
-
第一个函数重载中的
M
被推断为G<BX< int >, int>
。- 第二个函数重载具有应匹配的模板化类型。
T
可以从bx
推导出为int
T1<T>
模板模板类型,从bx
推导为BX< int >
M
将匹配任何内容。- 最后,您将第一个参数推导为
G<BX< int >, int>
,与第一个函数 的参数相同
GCC 也会引发歧义错误。
要在传递 G<...>
类型时优先使用第二个重载函数,您需要使用部分特化。 (因为它们的排名高于主要模板)。请参见
所示代码的明显意图是部分函数特化。
哪个...行不通。
那么,怎么办,怎么办...好吧,将部分函数专业化转换为普通模板如何?class 专业化?
我的解决方案在第一个函数参数类型上特化了一个模板,以消除静态 class 的歧义,并将其转发给两个最终 class 方法之一。
一个好的 C++ 编译器应该能够优化掉额外的函数调用层:
template <class T>
class BX {
public :
BX() {}
~BX() {}
};
template <class Tdata, class Tmetric>
class G {
public :
G() {}
~G() {}
};
template <template <class T> class T1, class T>
class DT {};
template<class M> class B_func;
class B {
public :
template <template <class T> class T1, class T, class M>
static void func(const M& m, const DT<T1, T>* dt, T1<T>& val)
{
B_func<M>::func(m, dt, val);
}
template <template <class T> class T1, class T, class M>
static void func_a(const M& m, const DT<T1, T>* dt, T1<T>& val) {}
template <template <class T> class T1, class T, class M>
static void func_b(const G<T1<T>, M>& g, const DT<T1, T>* dt, T1<T>& val) {}
//here is a small variation of func as a test
template <template <class T> class T1, class T, class M>
static void func2(const M& m, const DT<T1, T>* dt) {}
template <template <class T> class T1, class T, class M>
static void func2(const G<T1<T>, M>& g, const DT<T1, T>* dt) {}
};
template <class M>
class B_func {
public:
template<class two, class three>
static void func(const M& m, const two* dt, three& val)
{
B::func_a(m, dt, val);
}
};
template <template <class T> class T1, class T, class M>
class B_func<G<T1<T>, M>> {
public:
template<class two, class three>
static void func(const G<T1<T>, M>& m, const two* dt, three& val)
{
B::func_b(m, dt, val);
}
};
int main() {
BX< int > bx;
G<BX< int >, int> g;
DT<BX, int>* dt;
B::func(g, dt, bx);
B::func2(g, dt);
return 0;
}
这看起来像是 MSVC 和 GCC 中的错误。该调用应解析为第二个重载(Clang 和 EDG 正在这样做)。
对于调用 B::func(g, dt, bx)
,名称查找找到两个 func
模板。对它们中的每一个执行模板参数推导和替换,以生成随后可以参与重载决策的函数模板专业化声明。两个模板推导成功,剩下两个特化:
void B::func<BX, int, G<BX<int>, int>>(const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
void B::func<BX, int, int> (const G<BX<int>, int>&, const DT<BX, int>*, BX<int>&);
这两个函数具有相同的参数声明子句,因此重载决策显然无法根据调用参数的转换来区分它们;它必须求助于过程中的最后两个步骤。
首先,如果其中一个函数是模板特化而另一个不是,则首选非模板函数;此处不适用。
最后,它查看从中合成两个专业化声明的模板;如果根据函数模板的偏序,其中一个模板比另一个更特化,则首选相应的特化。 (这是原始模板重新发挥作用的过程中唯一的地方。)
下面的描述不是很准确,并且跳过了很多细节,但我试图将注意力集中在与本案例相关的部分。非常粗略:
首先,引用和 cv 限定符从两个模板的函数参数声明中剥离,产生:
F1(M , const DT<T1, T>*, T1<T>) F2(G<T2<U>, V>, const DT<T2, U>*, T2<U>)
(更改了模板参数名称以避免混淆)
然后,尝试推导,就好像使用另一个模板的函数参数形式作为参数调用一个模板,然后反过来。在这种情况下,最后两对对应的参数具有相同的形式,因此两种方式推导都成功。对于第一对对应的参数:
- 从
G<T2<U>, V>
形式的参数中推导出M
是可行的;M
推导为G<T2<U>, V>
. - 从
M
形式的参数中推导G<T2<U>, V>
中的T2
、U
和V
不起作用(M
可以是任何东西)。
换句话说,
G<T2<U>, V>
比M
是"more specific"形式;它不能表示M
可以表示的所有类型;这是 more specialized 试图在这种情况下形式化的直观含义。- 从
因此,推导适用于从
F2
到F1
的所有对应参数对,但反之则不然。这使得F2
在部分排序方面比F1
更专业。
这意味着对应于第二个模板的特化优先于重载决议。