【问题标题】:Porting C++ to C# - templates将 C++ 移植到 C# - 模板
【发布时间】:2011-01-19 13:34:19
【问题描述】:

我正在将 C++ 应用程序移植到 C#,并且已经跨模板运行。我已经阅读了一些关于这些的内容,并且我了解某些模板类似于 .Net 泛型。我阅读了这个案例的SO answer,它很好地总结了它。

但是,c++ 模板的某些用途似乎与泛型没有直接关系。在下面来自 Wikipedia 的 Template metaprogramming 文章的示例中,模板似乎接受了一个值,而不是一个类型。我不太确定这将如何移植到 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# 泛型?尝试阅读这篇文章msdn.microsoft.com/en-us/library/bb549151.aspx 了解 Func 的用法
  • 问题很可能不在于常量(与类型)作为模板的参数,而在于类或函数的特化和部分特化(如果它们当前在您的代码库中使用)。如果没有一些特定的例子,答案将需要广泛,并且可能不是真的有用。出于这个原因,我投票结束,请随意添加有关您在转换时遇到问题的某些代码的具体问题。
  • @nemke - 在此示例中,Factorial 模板在编译时展开 - 因此在运行时实际上没有完成任何工作。 OP 希望移植代码而不必将太多转换为不同类型的 C# 调用 - 所以如果可能的话,更喜欢基于泛型的解决方案。不幸的是,事实并非如此。 @David - 也很好
  • 顺便说一句,语义递归函数和模板执行相同的操作,它们的不同之处在于实现:模板被解析为编译时并扩展为常量,而等效的 C# 代码在运行时执行。无论如何,这可能是你现在能做的最好的翻译。但这只是一个玩具示例...

标签: c# .net c++ templates porting


【解决方案1】:

不幸的是,.Net 泛型只能接受类型。 C++ 模板采用编译器认为是常量表达式的其他值,因为它们实际上只是扩展为更多代码的宏。

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

return Factorial(N-1).Value;

【讨论】:

  • 还有一点,C# 和 C++ 编译器根本不同,因为 C++ 充满了允许在编译时编写代码的功能(宏和模板),而 C# 团队故意避开这 - 以可读性为理由。 C# 可能会更接近它 - 但它永远不会相同。
  • +1 C# 中的泛型只是为了方便,根据 MSDN 链接,无论如何,一切都是动态完成的,所以你获得的只是编译时类型检查(这是一件足够好的事情)但不能编译-时间计算(这是你的例子正在做的)。
  • @MatiasFG - 正是这一点 - 泛型是动态编译的,但其代码的类型检查是在泛型类型的编译时完成的。在成为一名全职 C# 开发人员之前,我确实怀念没有 C# 中的模板功能在 C++ 中开始进行模板元编程 - 但是泛型本身非常酷,我很高兴我们拥有它们!跨度>
【解决方案2】:

在下面的例子中……模板似乎接受一个值,而不是一个类型。

这不是你最大的问题。事实上,这在 C# 中理论上可以通过使用 Church numeral 或依赖嵌套泛型类型的 Peano 表示来解决。1

然而,你的问题是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 Link

    【讨论】:

    • 谢谢,它确实排除了这种情况:“C# 不允许非类型模板参数,例如模板 C {}。”。正如我在上述问题中所做的那样,重构为函数是移植这类模板的最佳方式吗?
    • 在上述情况下,重构为方法可能是最好的做法。但每种情况都需要根据具体情况进行处理。有时您可能希望/需要为类的构造函数提供值。
    • 好在 C# 不像 C++ 那样需要编译时常量——例如在声明数组时——所以在大多数情况下运行时计算会产生预期的结果。
    【解决方案4】:

    简短的回答是,并非所有可以在 C++ 模板中完成的事情都可以在 C# 泛型中完成。对于接受非类型值的模板,必须根据具体情况适当处理和重构每种情况。

    【讨论】:

      【解决方案5】:

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

      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 的小值。

      【讨论】:

      • 澄清一下,问题是您不能限制为数值。您可以对 struct 和 IComparable 设置一些限制,但这不足以确定数字/非数字。
      猜你喜欢
      • 2011-02-25
      • 1970-01-01
      • 2011-03-25
      • 1970-01-01
      • 2012-08-15
      • 1970-01-01
      • 2021-03-01
      • 1970-01-01
      • 2011-03-13
      相关资源
      最近更新 更多