我今天遇到了一个相当奇怪的重载解决方案.我把它减少到以下几点:
struct S
{
S(int, int = 0);
};
class C
{
public:
template <typename... Args>
C(S, Args... args);
C(const C&) = delete;
};
int main()
{
C c({1, 2});
}
Run Code Online (Sandbox Code Playgroud)
我完全希望C c({1, 2})匹配第一个构造函数C,其中可变参数的数量为零,{1, 2}并被视为S对象的初始化列表构造.
但是,我得到一个编译器错误,表明它匹配C的已删除的复制构造函数!
test.cpp: In function 'int main()':
test.cpp:17:15: error: use of deleted function 'C(const C &)'
test.cpp:12:5: error: declared here
Run Code Online (Sandbox Code Playgroud)
我可以看看它是如何工作的 - {1, 2}可以被解释为C的有效初始化器,它1是一个初始化器S(可以从int隐式构造,因为它的构造函数的第二个参数有一个默认值),以及2作为一个可变参数...但我不明白为什么那将是一个更好的匹配,尤其是看到有问题的复制构造函数被删除.
有人可以请解释这里正在使用的重载决策规则,并说明是否有一个解决方法不涉及在构造函数调用中提及S的名称?
编辑:由于有人提到代码片段使用不同的编译器编译,我应该澄清我在GCC 4.6.1中遇到了上述错误.
编辑2:我进一步简化了片段以获得更令人不安的失败:
struct …Run Code Online (Sandbox Code Playgroud) 所以我读了Eric Lippert的'Constraints不是签名的一部分',现在我明白规范指定在重载解析后检查类型约束,但我仍然不清楚为什么必须如此.以下是Eric的例子:
static void Foo<T>(T t) where T : Reptile { }
static void Foo(Animal animal) { }
static void Main()
{
Foo(new Giraffe());
}
Run Code Online (Sandbox Code Playgroud)
这不会编译,因为重载解析:Foo(new Giraffe())推断Foo<Giraffe>是最好的重载匹配,但是类型约束失败并抛出编译时错误.用埃里克的话说:
这里的原则是重载决策(和方法类型推断)找到参数列表和每个候选方法的形式参数列表之间的最佳匹配.也就是说,他们会查看候选方法的签名.
类型约束不是签名的一部分,但为什么不能呢?在某些情况下,考虑类型约束是签名的一部分是个坏主意吗?实施起来困难还是不可能?我并不是在提倡如果最佳选择的超载是出于无论什么原因无法调用的话,那么就会默默地回归到第二好的; 我讨厌那个.我只是想了解为什么不能使用类型约束来影响最佳过载的选择.
我想象在C#编译器内部,仅用于重载解析(它不会永久重写方法),如下所示:
static void Foo<T>(T t) where T : Reptile { }
Run Code Online (Sandbox Code Playgroud)
变成了:
static void Foo(Reptile t) { }
Run Code Online (Sandbox Code Playgroud)
为什么不能将类型约束"拉入"形式参数列表?这怎么会以任何不好的方式改变签名?我觉得它只会加强签名.然后Foo<Reptile>永远不会被视为超载候选者.
编辑2:难怪我的问题太混乱了.我没有正确阅读Eric的博客,我引用了错误的例子.我在示例中编辑了我觉得更合适.我还将标题更改为更具体.这个问题似乎并不像我最初想象的那么简单,也许我错过了一些重要的概念.我不太确定这是stackoverflow材料,这个问题/讨论可能最好转移到其他地方.
c# generics type-constraints overload-resolution nested-generics
我们来看下面的示例程序:
#include <cmath>
namespace half_float
{
template<typename T> struct half_expr {};
struct half : half_expr<half>
{
operator float() const;
};
template<typename T> half sin(const half_expr<T>&);
template<typename T> half atan2(const half_expr<T>&, const half_expr<T>&);
}
using namespace std;
using half_float::half;
int main()
{
half a, b;
half s = sin(a);
half t = atan2(a, b);
}
Run Code Online (Sandbox Code Playgroud)
在VS 2010中,这编译得很好(暂时忽略明显的链接器错误).但在VS 2012中,这给了我:
错误C2440:'转换':无法从'float'转换为'half_float :: half'
所以似乎重载解析不会从命名空间中选择版本half_float(ADL应该完成),而是std使用隐式转换来实现float.但奇怪的是,这只发生在atan2通话而非sin通话中.
在较大的项目中,这个错误实际上首先发生在我身上,它也发生在其他2参数函数(或者更确切地说是那些有2个half参数的函数)中fmod,但不适用于任何1参数函数.同样在较大的项目中它也适用于 …
使用VS2013,在下面的示例中,在尝试将函数传递给worker的构造函数时会给出两个不同的错误,但是,具有相同原型的lambda函数是可以的.
我做错了什么,如何更改GetA函数的定义以允许它被传递?
为了避免因误解类继承如何工作而引起的类似问题的混淆,我在这个例子中故意避免任何继承.
WorkerA只能接受Func<A>它的构造函数.
WorkerAorB更灵活,可以接受a Func<A>或a Func<B>.
WorkerAandB是最有能力的,可以接受a Func<A>和a Func<B>同时(或者任何一个).它最接近我的真实代码.
但是,当管理器中的代码尝试实例化工作程序时,WorkerA按预期工作,但WorkerAorB会给出错误:
错误CS0121:以下方法或属性之间的调用不明确:'WorkerAorB.WorkerAorB(System.Func <A>)'和'WorkerAorB.WorkerAorB(System.Func <B>)'
WorkerAandB 给
错误CS0407:'ManagerA.GetA()'的返回类型错误
在每种情况下,似乎编译器在传递对实际函数的引用而不是lambda或现有Func<A>变量时,无法确定要使用哪个重载,并且在WorkerAandB明确选择WRONG重载的情况下,给出关于传递函数的返回类型的错误.
class A { }
class B { }
class WorkerA
{
public WorkerA(Func<A> funcA) { }
}
class WorkerAorB
{
public WorkerAorB(Func<A> funcA) { }
public WorkerAorB(Func<B> funcB) { }
}
class WorkerAandB
{
public WorkerAandB(Func<A> funcA, Func<B> funcB = null) { } …Run Code Online (Sandbox Code Playgroud) 假设我在C#中有这个:
class OverloadTest
{
void Main()
{
CallWithDelegate(SomeOverloadedMethod);
}
delegate void SomeDelegateWithoutParameters();
delegate void SomeDelegateWithParameter(int n);
void CallWithDelegate(SomeDelegateWithoutParameters del) { }
void CallWithDelegate(SomeDelegateWithParameter del) { }
void SomeOverloadedMethod() { }
void SomeOverloadedMethod(int n) { }
}
Run Code Online (Sandbox Code Playgroud)
当然,这不会编译,因为该行CallWithDelegate(SomeOverloadedMethod);是模糊的.
现在,假设只有一个CallWithDelegate(SomeDelegateWithoutParameter del)函数(没有重载).在这种情况下,没有歧义,因为从似乎正在发生的事情,编译器可以查看参数类型并SomeOverloadedMethod(int n)从候选列表中丢弃(因为它只能占用a SomeDelegateWithoutParameters),因此它编译.
我不打算写这样的代码; 从编译器编写者的角度来看,这只是出于好奇.我找不到关于这个问题的答案,因为用语言表达是很困惑的.
我想知道C#中是否有任何方法可以消除Main()给定示例中的调用,以便编译.如何指定它以便它被解析为CallWithDelegate(SomeDelegateWithoutParameters del)传递SomeOverloadedMethod()或被CallWithDelegate(SomeDelegateWithParameter del)传递SomeOverloadedMethod(int n)?
以下是模板上下文中一个非常奇怪的重载决策的极简主义示例:
#include <iostream>
// Types //
struct I { int v; };
template <class T>
struct D { T t; };
// Functions //
// Overload 1
template <class T>
I f(T) { return {1}; }
// Template indirection that calls f(T)
template <class T>
I g(D<T>) { return f(T{}); }
// Non template indirection that calls f(T)
I h(D<I>) { return f(I{}); }
int main() {
std::cout
<< f(I{}).v // f(I{}) overload called directly
<< "\n" // => overload …Run Code Online (Sandbox Code Playgroud) 考虑一下这段代码:
template<typename FirstArg>
void foo()
{
}
template<typename FirstArg, typename... RestOfArgs>
void foo()
{
foo<RestOfArgs...>();
}
int main()
{
foo<int, int, int>();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
foo<RestOfArgs...>();当RestOfArgs只有一个元素({int})时,它不会因为模糊调用而编译.
但这编译没有错误:
template<typename FirstArg>
void foo(FirstArg x)
{
}
template<typename FirstArg, typename... RestOfArgs>
void foo(FirstArg x, RestOfArgs... y)
{
foo(y...);
}
int main()
{
foo<int, int, int>(5, 6, 7);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
为什么第一种情况存在歧义?
为什么第二种情况没有歧义?
考虑下面的代码。
struct A {
int i;
};
struct B {
char c;
};
struct C {
double d;
};
void f(A a);
void f(B b);
void f(C c);
void g()
{
f({5});
}
Run Code Online (Sandbox Code Playgroud)
在这里,我感到模棱两可f({5});。但似乎struct A的构造函数与完全匹配{5},而第二个则需要整数提升,而最后一个需要浮点转换。
那么为什么会有歧义呢?
在下面的程序中 structB定义了两个转换运算符: toA和 to const A&。然后A从 -object 创建B-object:
struct A {};
struct B {
A a;
B() = default;
operator const A&() { return a; }
operator A() { return a; }
};
int main() {
(void) A(B{});
}
Run Code Online (Sandbox Code Playgroud)
该计划是
GCC 错误消息是
error: call of overloaded 'A(B)' is ambiguous
note: candidate: 'constexpr A::A(const A&)'
note: candidate: 'constexpr …Run Code Online (Sandbox Code Playgroud) 如果参数之一是占位符类型,则在非类型模板参数的情况下选择重载函数模板的规则是什么。我对编译器当前的行为感到困惑,请考虑下一个示例:
template<int N> struct A{};
template<auto... N> void f(A<N...>);
template<int N> void f(A<N>) {}
template<auto N> void g(A<N>);
template<int N> void g(A<N>) {}
int main() {
f( A<1>{} ); // ok in GCC and Clang, error in MSVC
g( A<1>{} ); // ok in GCC, error in Clang and MSVC
}
Run Code Online (Sandbox Code Playgroud)
这里 MSVC 无法在两组重载之间进行选择。另一方面,GCC总是选择类型更具体的函数模板<int>。Clang 有点处于中间位置,它<int>比参数 pack 更受欢迎,但它在和重载<auto...>之间的选择过程中发现了歧义。在线演示: https: //gcc.godbolt.org/z/4EK99Gjhx<int><auto>
哪一项行为是正确的或者标准没有明确规定?
c++ templates partial-ordering language-lawyer overload-resolution