将C++移植到C# - 模板

5
我正在将一个C++应用程序移植到C#,遇到了模板。我已经阅读了一些相关内容,了解到有些模板类似于.Net泛型。我阅读了这个SO答案,它很好地总结了这个问题。
然而,某些c++模板的用法似乎与泛型无直接关系。在维基百科模板元编程文章中的下面例子中,模板似乎接受一个值,而不是一个类型。我不太确定如何将其移植到C#?
template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

显然,对于这个例子,我可以这样做:

public int Factorial(int N){
    if(N == 0) return 1;
    return Factorial(N - 1);
}

但在我看来,这似乎是将代码重构为一个函数,而不是移植到语义相似的代码。


你为什么要在C#泛型中使用经典递归函数?尝试阅读这篇文章http://msdn.microsoft.com/en-us/library/bb549151.aspx,了解Func<T, TResult>的用法。 - nemke
1
问题很可能不在于将常量(与类型)作为模板参数,而在于类或函数的特化和部分特化是否在您的代码库中被使用。如果没有具体的示例,答案将需要比较宽泛,可能并不真正有用。我投票关闭此问题,出于这个原因,请随时添加关于您遇到转换问题的某些代码片段的具体问题。 - David Rodríguez - dribeas
1
在这个例子中,阶乘模板在编译时展开 - 因此在运行时实际上并没有做任何工作。OP想要将代码移植而不必将太多内容翻译成不同类型的C#调用 - 因此如果可能的话,他会更喜欢基于泛型的解决方案。不幸的是,这是不可能的。@David - 也非常好的观点。 - Andras Zoltan
顺便说一下,从语义上讲,递归函数和模板执行相同的操作,它们的区别在于实现:模板在编译时解析并扩展为常量,而等效的C#代码在运行时执行。无论如何,这可能是您现在可以做的最好的翻译。但这只是一个玩具示例... - David Rodríguez - dribeas
5个回答

5

很遗憾,.Net泛型只能接受类型。C++模板可以接受被编译器视为常量表达式的其他值,因为它们实际上只是将代码扩展为更多代码的宏。

这意味着将代码转换为方法调用是最好的选择。您可以使方法调用返回具有.Value属性的类型(根据您的示例),从而使移植的代码类似于模板:

return Factorial(N-1).Value;

另外一个要点是,C#和C++编译器在根本上是不同的,因为C++中充斥着允许在编译时编写代码的功能(宏和模板),而C#团队则故意避开了这一点——他们以可读性为理由。C#可能会越来越接近这一点,但它永远不会完全相同。 - Andras Zoltan
在C#中,泛型只是一种方便的方式,根据MSDN链接,所有事情都是动态完成的,所以你得到的只是编译时类型检查(这很好),而不是编译时计算(这就是你的例子在做什么)。 - MatiasFG
@MatiasFG - 正是这个意思 - 泛型是动态编译的,但其代码的类型检查是在泛型类型的编译时完成的。我确实想念在C#中没有模板功能,因为我在成为全职C#开发人员之前刚开始进行C++模板元编程 - 但是泛型本身非常酷,我很高兴我们有它们! - Andras Zoltan

5
在下面的例子中... 模板似乎接受一个值,而不是类型。 这不是你最大的问题。实际上,从理论上讲,这可以通过使用基于嵌套泛型类型的Church numeral或Peano表示来在C#中解决。 但是,你的问题在于C#不允许模板特化。在你的例子中,模板特化负责定义0的阶乘为1,而不是与所有其他数字相同。 C#不允许这样做。 因此,在递归模板(通用)定义中没有指定基本情况的方法,因此没有递归。 C#泛型不是图灵完备的,而C ++模板是。

1这样的东西:

class Zero { }

class Successor<T> : Zero where T : Zero { }

// one:
Successor<Zero>
// two:
Successor<Successor<Zero>>
// etc.

对这些数字实施操作留给读者作为练习。


3

请查看这篇文章,了解C#泛型和c++模板之间的区别:

我认为你的例子已经包含在其中。

MSDN链接


谢谢,这不包括这种情况:“C#不允许非类型模板参数,例如template C<int i> {}。”。像我在上面的问题中所做的那样重构为一个函数,是移植这些模板的最佳方法吗? - Iain Sproat
1
在上述情况下,重构成一个方法可能是最好的选择。但每种情况都需要根据具体情况处理。有时您可能希望/需要向类的构造函数提供一个值。 - Wes P
1
好消息是,C#不像C++那样需要编译时常量——例如在声明数组时——因此,在大多数情况下,运行时计算将产生预期的结果。 - David Rodríguez - dribeas

1
简而言之,C++模板所能实现的并非所有都可以通过C#泛型来实现。对于接受非类型值的模板,每种情况都需要基于实际情况进行处理和重构。

0

这是我能想到的最接近的:

public class Factorial<T>
    where T : IConvertible 
    {
        public T GetFactorial(T t)
        {
            int int32 = Convert.ToInt32(t);
            if (int32 == 0)
                return (T) Convert.ChangeType( 1, typeof(T));
            return GetFactorial( (T) Convert.ChangeType(int32-1, typeof(T)) );
        }
    }

问题在于你无法定义泛型并将其限制为 ValueTypes。这适用于 byte、Int16 和 Int32。也适用于较小的 Int64 值。

请注意,问题在于您无法限制为数字值。您可以对结构和IComparable设置一些约束,但这还远远不足以确定数字/非数字。 - Wes P

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接