【问题标题】:Is difference of two constexpr instances of __func__ pointers still constexpr?__func__ 指针的两个 constexpr 实例的差异仍然是 constexpr 吗?
【发布时间】:2020-04-18 02:53:23
【问题描述】:

这是有效的 C++ 吗?

int main() {
    constexpr auto sz = __func__ - __func__;
    return sz;
}

GCC 和 MSVC 认为可以,Clang 认为不行:Compiler Explorer


所有编译器都同意这个没问题:Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = p;
    constexpr auto sz = p2 - p;
    return sz;
}

clang再次不喜欢这个,但其他人对它没问题:Compiler Explorer

int main() {
    constexpr auto p = __func__;
    constexpr auto p2 = __func__;
    constexpr auto sz = p2 - p;
    return sz;
}

这里有什么?我认为在不相关的指针上的算术是未定义的行为,但__func__返回相同的指针,否?我不确定,所以我想我可以测试一下。如果我回忆起来,std::equal_to可以比较无关的指针没有未定义的行为:

#include <functional>

int main() {
    constexpr std::equal_to<const char*> eq{};
    static_assert(eq(__func__, __func__));
}

clang认为@ 987654337不是一个常量表达式,即使@ 987654338 is constexpr。其他编译器不抱怨:Compiler Explorer


Clang 也不会编译这个。抱怨__func__ == __func__不是常量表达式:Compiler Explorer

int main() {
    static_assert(__func__ == __func__);
}

【问题讨论】:

  • 来自Function_definition__func__ 就像static const char __func__[] = "function-name"; 一样被接受Demo...
  • 有趣的是,如果您使用 __func__ 初始化一个 constexpr 变量并在 static_assert 中使用它,它就会起作用...
  • @Jarod42 所以这是 Clang 中的一个错误?
  • @florestan 喜欢this?它也不会用 Clang 编译。我在问题中的第二个和第三个例子就是你提到的方式。一个编译,另一个不编译。
  • 另请参阅 CWG1962,这可能会从 constexpr 评估中完全删除 __func__

标签: c++ pointers language-lawyer c++17 constexpr


【解决方案1】:

__func__ 在 C++ 中是一个标识符。特别是,它引用了一个特定的对象。来自[dcl.fct.def.general]/8

函数局部预定义变量_­_­func_­_­被定义为表单的定义

static const char __func__[] = "function-name";

已提供,其中 function-name 是实现定义的字符串。未指定此类变量的地址是否与程序中任何其他对象的地址不同。

作为function-local predefined variable,这个定义(好像)出现在功能块的开头。因此,在该块中对__func__ 的任何使用都将引用该变量。

至于“任何其他对象”部分,变量定义了一个对象。 __func__ 命名由该变量定义的对象。因此,在一个函数中,所有__func__ 的使用都命名同一个变量。未定义的是该变量是否是与其他对象不同的对象。

也就是说,如果你在一个名为 foo 的函数中,并且你在问题的其他地方使用了文字 "foo",则不禁止实现让变量 __func__ 也相同文字 "foo" 返回的对象。也就是说,标准并不要求出现__func__ 的每个函数都必须将数据与字符串文字本身分开存储。

现在,C++ 的“好像”规则允许实现偏离这一点,但他们不能以可检测的方式做到这一点。因此,虽然变量本身可能有也可能没有与其他对象不同的地址,但在同一个函数中使用 __func__ 必须表现得好像它们引用的是同一个对象。

Clang 似乎没有以这种方式实现__func__。它似乎将其实现为好像它返回了函数名称的纯右值字符串文字。两个不同的字符串文字不必引用同一个对象,因此减去指向它们的指针就是 UB。并且常量表达式上下文中的未定义行为是格式错误的。

唯一让我犹豫说 Clang 在这里 100% 错误的是[temp.arg.nontype]/2

对于引用或指针类型的非类型模板参数,常量表达式的值不应引用(或对于指针类型,不应是地址):

...

  • 预定义的_­_­func_­_ 变量。

看,这似乎允许在实现中进行一些捏造。也就是说,虽然__func__ 在技术上可以是一个常量表达式,但您不能在模板参数中使用它。它被视为字符串文字,即使它在技术上是一个变量。

所以在某种程度上,我会说标准是从它的嘴里说出来的。

【讨论】:

  • 所以,严格来说,__func__ 在我的问题的所有情况下都可以是一个常量表达式,对吧?所以代码应该已经编译好了。
  • “未指定此类变量的地址是否与程序中任何其他对象的地址不同。”部分?未指定的行为意味着抽象机器行为的不确定性。这对 constexpr 评估有问题吗?如果__func__ 第一次出现的地址与另一个对象的地址相同,而__func__ 的第二次出现又不是呢?当然,这并不意味着两个实例之间的地址不同,但我仍然感到困惑!
  • @JohannesSchaub-litb:““未指定此类变量的地址是否与程序中任何其他对象的地址不同。”部分呢?”怎么样? __func__ 不是宏;它是一个标识符,它命名一个特定的变量,因此是一个特定的对象。因此,在同一函数中对__func__ 的任何使用都应导致引用同一对象的泛左值。或者更重要的是,它不能以不会出现这种情况的方式实现。
  • @Nicol 指的是同一个对象。但是该对象可能在某一时刻与另一个对象具有相同的地址。而在另一个瞬间没有。我并不是说这是一个问题,但我只是提醒大家这种可能性。然后毕竟我也可能有误会,所以我也是希望得到纠正或确认。
  • @JohannesSchaub-litb:“但该对象可能在某一时刻与另一个对象具有相同的地址。”在 C++ 对象模型下这是不允许的。两个对象,一个都没有嵌套在另一个对象中,不能在它们的生命周期内同时在同一个存储中。并且有问题的对象具有静态存储持续时间,因此除非您在其上使用placement-new,否则在程序结束之前它不会去任何地方。
猜你喜欢
  • 2019-10-06
  • 1970-01-01
  • 2012-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
相关资源
最近更新 更多