【问题标题】:Is std::less supposed to allow comparison of unrelated pointers at compile-time?std::less 是否应该允许在编译时比较不相关的指针?
【发布时间】:2020-10-10 23:48:30
【问题描述】:

考虑这段代码:

#include <functional>
#include <typeinfo>

template <typename T>
inline constexpr const void *foo = &typeid(T);

int main()
{
    constexpr bool a = std::less<const void*>{}(foo<int>, foo<float>);
} 

Run on gcc.gotbolt.org

如果我在这里使用&lt; 而不是std::less,则代码无法编译。这并不奇怪,因为如果指针指向不相关的对象,则关系指针比较的结果是 unspecified,显然这样的比较不能在编译时完成。

<source>:9:20: error: constexpr variable 'a' must be initialized by a constant expression
    constexpr bool a = foo<int> < foo<float>;
                   ^   ~~~~~~~~~~~~~~~~~~~~~
<source>:9:33: note: comparison has unspecified value
    constexpr bool a = foo<int> < foo<float>;
                                ^

即使我使用std::less,代码仍然无法编译。编译器错误是一样的。 std::less 似乎至少在 libstdc++ 和 libc++ 中实现为 &lt;;我在 GCC、Clang 和 MSVC 上得到了相同的结果。

但是,关于 std::less 的 cppreference 页面声称:

  1. 它的operator()constexpr

  2. 它神奇地实现了对指针的严格全序,即可用于将不相关的指针与合理的结果进行比较。

那么,这是所有这些编译器中的一个错误,还是我遗漏了一些关于 std::less 的细节导致上面的代码格式错误?

【问题讨论】:

  • 离题:在比较之前将两个指针都转换为 uintptr_t 对我来说不会出现 'magical'...
  • @Aconcagua 如果是这样,那它就不可能是constexpr
  • 找到another relevant paragraph in cppreference(在本节末尾)。
  • 如果你不使用a,它可以简单地优化掉。如果你使用它会发生什么?例如,return a ? 99 : 101 ;
  • @TonyK 即使不使用变量,代码也不会编译,所以我怀疑如果我以某种方式使用它会编译。

标签: c++ language-lawyer std compile-time


【解决方案1】:

我认为您提出的问题没有明确的答案。这是LWG 2833 的一个特定情况:标记库函数constexpr 并不能解释调用函数会产生常量表达式的情况。

在解决此问题之前,我认为您根本不能依赖 std::less 能够在编译时比较不相关的指针。

【讨论】:

    【解决方案2】:

    要成为有效的constexpr函数,它应该有结果为constexpr的参数,而不是所有参数。

    例如

    constexpr int foo(bool b) { if (!b) throw 42; return 42; }
    

    有效,f(true) 可以在 constexpr 中使用(即使 f(false) 不能)。

    constexpr int a[2]{};
    constexpr bool b = std::less<const void*>{}(&a[0], &a[1]);
    

    有效且足以让less::operator() 成为constexpr

    我认为标准中的 constexpr 没有指定哪些范围/值是正确的。

    所以所有编译器都是正确的。

    【讨论】:

    • 这如何适用于这个特定问题?
    • @_Static_assert:可能是吹毛求疵,但是标记为constexpr(带有常量参数)的函数通常不足以用于常量表达式。
    【解决方案3】:

    在您的问题中,您声明了一个像constexpr bool a = std::less&lt;const void*&gt;{}(foo&lt;int&gt;, foo&lt;float&gt;); 这样的变量,需要一个 constexpr 变量来满足以下规则:

    对象声明中使用的 constexpr 说明符将对象声明为 const。这样的对象应具有文字类型并应被初始化。在任何 constexpr 变量声明中,初始化的完整表达式应为常量表达式。 (注意强调部分)

    常量表达式必须是核心常量表达式,因此它必须满足核心常量表达式规则:

    结果为未指定关系或等式运算符;

    在您的代码中,&amp;typeid(int)&amp;typeid(float) 未指定,原因是:

    比较不相等的指向对象的指针定义如下:

    • 如果两个指针指向同一数组的不同元素或其子对象,则指向具有较高下标的元素的指针比较大。
    • 如果两个指针指向同一对象的不同非静态数据成员,或指向此类成员的子对象,递归地,如果两个成员具有相同的访问控制并提供它们的类,则指向后面声明的成员的指针会比较大不是工会。
    • 否则,两个指针的比较都不会大于另一个

    &amp;typeid(int)&amp;typeid(float) 符合第三个要点。和

    如果两个操作数 p 和 q 比较相等,则 p=q 都得出真值,而 pq 都得出假值。否则,如果指针 p 比较大于指针 q,则 p>=q、p>q、q=p,并且q>p 都产生错误。 否则,每个运算符的结果都是未指定的

    所以,&amp;typeid(int)&amp;typeid(float) 的比较结果是不确定的,因此它不是一个常量表达式。

    【讨论】:

    • @HolyBlackCat 修改了我的答案。
    • 是的,现在看起来是对为什么&lt; 在这种情况下在编译时不起作用的正确解释。但是,仍然不清楚是否应该使用一些编译器魔法而不是 &lt; 来实现 less 以使其在编译时工作,即使对于不相关的指针也是如此。 (根据布赖恩的回答,标准没有具体说明是否应该这样做。)
    • @HolyBlackCat 据我所知,它不能因为整个typeid 是实现定义的,如果操作数是多态类类型,它将是一个运行时值。
    猜你喜欢
    • 2013-12-07
    • 1970-01-01
    • 1970-01-01
    • 2017-02-16
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 2015-03-13
    相关资源
    最近更新 更多