【问题标题】:C11 _Generic usageC11 _通用用法
【发布时间】:2016-10-17 22:20:31
【问题描述】:

我试图学习如何使用“新的”C11 Generic 表达式,但我碰壁了。

考虑以下代码:

#include <stdlib.h>
#include <stdio.h>

#define test(X, Y, c) \
    _Generic((X), \
        double:     _Generic((Y), \
                        double * :  test_double, \
                        default: test_double \
                    ), \
        int:        _Generic((Y), \
                        int * : test_int, \
                        default: test_int \
                    ) \
    ) (X, Y, c)


int test_double(double a, double *b, int c);
int test_int(int a, int *b, int c);

int test_double(double a, double *b, int c) { return 1; }
int test_int(int a, int *b, int c) { return 2; }

int main()
{
    double *t = malloc(sizeof(double));
    int *s = malloc(sizeof(int));
    int a1 = test(3.4, t, 1);
    int i = 3;
    int a2 = test(i, s, 1);
    printf("%d\t", a1);
    printf("%d\n", a2);
    return 0;
 }

这一切都很好,但我仍然不明白为什么“_Generic((Y), ...”中的那些默认情况是必要的,而我可以在“_Generic((X), . .." 没有后果。

事实上,如果我删除这两个默认值,我会收到一个错误 (gcc 5.4.0),说 "selector of type 'double *' is not compatible with any association" while macro-expanding " int a1 = test(3.4, t, 1);"在宏扩展 test(i, s, 1)

时与 "int *" 相同

“默认”真的有必要还是我遗漏了什么? 在第一种情况下,为什么会这样呢?如果我只有 test_double 和 test_int 可以调用,为什么我应该为甚至不应该编译的东西设置默认情况?

【问题讨论】:

  • 您的描述令人困惑。如果您删除 Generic 中的默认值(Y 会出现错误?
  • 是的,没错。粗体字。

标签: c generics macros default c11


【解决方案1】:

_Generic 不幸的是在标准中未指定。通常的解释似乎是非选定情况下的表达式不得包含任何违反约束的情况。

一个更简单的例子:

int main(void)
{
    int x;

    _Generic(0, int: x = 5, float: x = (void)0);
}

此代码在 gcc 中违反了约束,因为它对所有关联的表达式(不仅仅是选定的表达式)执行约束检查,并且 x = (void)0 包含违反约束。


将这个原则应用到你的代码没有默认情况下,我们看到的问题是当宏被实例化为Y作为一个变量声明为int *s时,那么相关的表达式之一是_Generic(s, double * : test_double),它是违反约束的,因为没有匹配任何大小写。

【讨论】:

  • 您知道是否有缺陷报告/努力改善这种情况?我找不到任何东西,所以我现在正在尝试做必要的工作。
  • @DanielJour 不确定,抱歉。 Here is a list 目前正在为 C2X 制定的所有提案
【解决方案2】:

TL;DR

选择发生在编译时,但这意味着其他(未选择)代码被丢弃。它仍然必须是有效的,这意味着......

如果不使用默认值并且没有一个类型名称与控制表达式的类型兼容,则程序将无法编译。

(Source)


这是一个令人惊讶的:

没有“第一个 Y”的默认情况:

#define test(X, Y, c) \
    _Generic((X), \
        double:     _Generic((Y), \
                        double * :  test_double \
                    ), \
        int:        _Generic((Y), \
                        int * : test_int, \
                        default: test_default \
                    ) \
    ) (X, Y, c)

我得到了那个错误:

prog.c:6:30: 错误:“int *”类型的“_Generic”选择器与任何关联都不兼容

请注意,它抱怨 int * 不兼容!为什么? 好吧,让我们看看报告的行:

    int a2 = test(i, s, 1);

Xint 类型,Yint * 类型。

现在是重要的部分:扩展发生无条件。所以即使Xint 类型,X 的第一个关联(当它是double 类型时)必须是一个格式良好的程序。因此,Yint *,以下内容必须是正确的:

_Generic((Y), \
             double * :  test_double \
              ), \

而且由于int * 不是double *,所以这里出了问题。


我只是查看标准(实际上是 N1570),但找不到任何实际上明确指定此行为的内容。我想在这种情况下可以报告缺陷,标准对此过于模糊。我现在正在尝试这样做。

【讨论】:

    【解决方案3】:

    这显然是因为嵌套的 _Generic 选择。

    问题在于,对于 _Generic,必须在编译时满足所有可能的泛型关联的类型兼容性。即使只选择了一个关联,未选择的关联也必须有一些兼容的关联。

    关联默认处理与该 _Generic 选择的其余关联不兼容的每个关联。

    假设您删除了int: _Generic((Y), 上的默认关联。如果这样做,则选择了双关联,但 int 关联仍然必须处理双 * 类型,这通常是默认完成的。在这种情况下,只有 int* 关联,它会输出错误。

    【讨论】:

      【解决方案4】:

      这是优先顺序。在您的代码中,两个_Generic((Y)_Generic((X) 之前运行。它们在括号中。编译器渴望并为test(3.4, t, 1)test(i, s, 1) 运行。

      我会稍微重写一下代码。

      #define test(X, Y, c) \
          _Generic((X), \
              double:     _Generic((Y), \
                          double * :  test_double, \
                          default : test_error \
                      ), \
              int:        _Generic((Y), \
                          int * : test_int, \
                          default : test_error \
                      ) \
          ) (X, Y, c)
      
      static void test_error(void)
      {
          /* This function should be optimised away by compiler. */
          assert(0);
      }
      

      【讨论】:

      • 优先级和括号在这里不相关
      • @M.M 我的更改测试了两个参数,应该在不受支持的对上抛出很好的错误。有问题的代码只测试第一个。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多