【问题标题】:Is there an equivalent in C for C++ templates?C++ 模板在 C 中是否有等价物?
【发布时间】:2010-05-20 12:54:53
【问题描述】:

在我正在编写的代码中,我需要 foo(int, char*)foo(int, int) 函数。

如果我用 C++ 编写代码,我会使用模板。 C有什么等价物吗?或者我应该使用 void 指针?怎么样?

【问题讨论】:

  • 有时您还可以使用宏来模拟模板行为。
  • 在 C++ 中,您将使用覆盖而不是模板
  • @Motti 重载,不是吗?
  • @Amarghosh,当然是我的错。

标签: c


【解决方案1】:

我认为 C 语言中最接近模板的是一些丑陋的宏代码。例如,定义一个返回两倍参数的简单函数:

#define MAKE_DOUBLER(T)  \
    T doubler_##T(T x) { \
        return 2 * x;    \
    }

MAKE_DOUBLER(int)
MAKE_DOUBLER(float)

请注意,由于 C 没有函数重载,因此您必须使用函数名称来玩弄技巧(上面同时生成了 doubler_intdoubler_float,您必须以这种方式调用它们)。

printf("%d\n", doubler_int(5));
printf("%f\n", doubler_float(12.3));

【讨论】:

  • 顺便说一句,跟随你的维护程序员(也可能是你)将欣赏这种技术。如果您看到对doubler_int() 的调用并且不知道宏,您将如何找到该函数?搜索源代码是找不到的。
  • 在哪里写 MAKE_DOUBLER(int) 和 MAKE_DOUBLER(float) ?
  • @ShehbazJaffer 任何你通常声明函数的地方。
  • 一些编译器不喜欢这样 - #define 是一个预编译器,因此函数(宏纹理数据)将被内联。您不能在 Visual Studio 的函数中逐字地内联函数。然而,这可能适用于 gcc,但不能作为 printf 示例中的参数。模板比 #defines 更聪明,因为编译器可以决定是像 #define 一样内联还是为模板提供的新生成函数创建符号 - 非常非常强大。
【解决方案2】:

你不能那样做。
在 C 中没有重载,一个函数,一个名称,您需要使用支持您所有需求的类型,例如(空 *)

要么这样做,要么做一个foo_int(int,int) 和一个foo_char(int, char*)

【讨论】:

  • 我同意。通常的 C 解决方案由带有类型后缀的函数名组成。著名的例子:atoi, atol, atoll (IIRC) 和 OpenGL glVector3f, glVector2f, glColor3f, ...
  • 但如果我使用 void* 我该如何检查数据的类型?没有 c isInteger 和 isCharPointer,对吧?
  • 你不能。如果你事先不知道它是什么,你需要传递另一个参数来区分。但这有点难看,我会实现 foo_intfoo_char
  • 该语言没有通用编程的特定工具这一事实并不意味着无法克服这一缺陷。
【解决方案3】:

是的,有。您可以在C11 中使用类型泛型表达式:

#include <stdio.h>

void foo_char_ptr(int a, char *b) {
  printf("Called int, char*.\n");
}

void foo_int(int a, int b) {
  printf("Called int, int.\n");
}

#define foo(a, b) _Generic((b), char*: foo_char_ptr, int: foo_int)(a, b)

int main() {
  foo(1, 1);
  foo(1, "foo");
}

// Output:
// Called int, int.
// Called int, char*.

【讨论】:

    【解决方案4】:

    模板可以使用模板头实现。

    让 foo.h 像这样:

    #ifndef PREFIX
        #define PREFIX
    #endif
    #define CCAT2(x, y) x ## y
    #define CCAT(x, y) CCAT2(x, y)
    #define FN(x) CCAT(PREFIX, x)
    
    #ifndef T
        #error Template argument missing.
    #endif
    
    void FN(foo)(int x, T t)
    {
        // Whatever.
    }
    
    
    #undef T
    #undef PREFIX
    #undef CCAT2
    #undef CCAT
    #undef FN
    

    要使用它,您可以这样做:

    #define T char*
    #define PREFIX pchar_
    #include "foo.h"
    
    #define T int
    #define PREFIX int_
    #include "foo.h"
    

    现在您有了可以使用的pchar_foo()int_foo()

    这样做的好处是,如果存在构建问题,您会在模板头中获得行号,而不是编译器只是说宏错误,并且代码完成在某些 IDE 中也可以工作。

    PREFIXCCATFN 宏非常常见,因此我将它们的定义提取到单独的标题中,并将它们的未定义提取到另一个标题中。

    为了好玩,我使用这种模式实现了部分 STL,并将它用于我的一些 C 项目中。

    【讨论】:

    • 聪明,我喜欢你不止一次地包含 .h。最佳答案
    【解决方案5】:

    其他人已经讨论过 c 在重载方面的内在限制。但是请注意,如果您可以推断出需要哪种情况,您可以使用可变参数:

    #include <stdarg.h>
    foo(int, ...);
    

    如果不能推导出来,可以多传一个参数:

    foo(int, char *spec, ...);
    

    spec 告诉函数在后续参数中期望什么。像 printfscanf 系列函数一样。事实上,您可能会发现重用 printf/scanf 约定来指定类型很方便,从而使您的用户不必依赖另一种迷你语言。

    【讨论】:

      【解决方案6】:

      您还可以使用联合来代替 void* 来保存您需要的任何类型的数据:

      typedef struct {
          int type;
          union {
              char* char_ptr;
              int int_val;
              // etc...
          };
      } foo_data;
      
      void foo(foo_data data)
      {
          switch (data.type) {
              case 0:
                  printf("%s\n", data.char_ptr);
                  break;
              case 1:
                  printf("%i\n", data.int_val);
                  break;
          }
      }
      
      void main()
      {
          foo_data data;
      
          data.type = 0; data.char_ptr = "hello";
          foo(data);
          data.type = 1; data.int_val  = 12;
          foo(data);
      }
      

      当然,您应该为类型值创建常量。

      【讨论】:

      • 正要写这个答案,但你已经写好了。
      【解决方案7】:

      您可以使用来自 GCC 的 _Generic。 (虽然这不能移植到其他编译器) http://www.robertgamble.net/2012/01/c11-generic-selections.html

      【讨论】:

      • Mirek Rusin 的回答似乎已经表明了这一点。为什么它不便携?这是 C11 功能。
      • 这已经被回答了。请不要发布重复的答案。在此处发布任何答案之前,您是否进行了适当的研究。谢谢
      • 在 2012 年发布一个关于 2011 年发布的功能抱怨兼容性问题的 7 年后发布的博客链接,这是相当不诚实的......
      • 为避免疑问,Clang was broken,它已在 Clang 3.8.0 中修复。 Clang 的当前版本是 8
      猜你喜欢
      • 1970-01-01
      • 2013-06-21
      • 1970-01-01
      • 2016-03-17
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-10
      相关资源
      最近更新 更多