How can I write a generic equality comparison that will fall back to operator== when std::cmp_equal won't compile?

huangapple go评论59阅读模式
英文:

How can I write a generic equality comparison that will fall back to operator== when std::cmp_equal won't compile?

问题

我想要一个通用的相等性检查函数,它在可能的情况下更喜欢使用std::cmp_equal,因为它具有优点,但对于std::cmp_equal无法处理的类型,比如bool或用户定义的类型,它将使用operator==。

这似乎应该是一个对requires来说很容易的工作,但我惊讶地发现,尽管这对于libc++来说可以工作,但对于libstdc++来说失败了,并显示了静态断言消息。

#include <utility>

template <typename T, typename U>
auto generic_equal(T t, U u) {
    if constexpr (requires { std::cmp_equal(t, u); }) {
        return std::cmp_equal(t, u);
    } else {
        return t == u;
    }
}

struct A {
    friend bool operator==(A,A) = default;
};

int main() {
    generic_equal(false, false);
    generic_equal(-1, 5);
    generic_equal(A{}, A{});
}

有没有一种好方法可以说服编译器在requires处而不是在调用处意识到它不能实例化std::cmp_equal?

使用libc++成功,使用libstdc++失败的Compiler Explorer链接:https://godbolt.org/z/nKzfE87Ye

英文:

I want a generic equality-checking function that prefers to use std::cmp_equal when possible because of its virtues, but will use operator== for types that std::cmp_equal can't handle, such as bool or user-defined types.

This seemed like it should be an easy job for requires, but I was surprised to discover that although this works for libc++, it fails for libstdc++ with static_assert messages.

#include &lt;utility&gt;

template &lt;typename T, typename U&gt;
auto generic_equal(T t, U u) {
    if constexpr (requires { std::cmp_equal(t, u); }) {
        return std::cmp_equal(t, u);
    } else {
        return t == u;
    }
}

struct A {
    friend bool operator==(A,A) = default;
};

int main() {
    generic_equal(false, false);
    generic_equal(-1, 5);
    generic_equal(A{}, A{});
}

Is there a good way to convince the compiler to realize it can't instantiate std::cmp_equal at the requires instead of at the call?

Compiler explorer link showing success with libc++ and failure with libstdc++: https://godbolt.org/z/nKzfE87Ye

答案1

得分: 1

std::cmp_equal要求模板参数必须是标准整数类型扩展整数类型,这在[basic.fundamental]中有定义:

> 1. 有五种标准有符号整数类型:“signed char”,“short int”,“int”,“long int”,和“long long int”。在这个列表中,每种类型至少提供与列表中前面的类型一样多的存储空间。也可能存在实现定义的扩展有符号整数类型。[...]
>
> 2. 对于每种标准有符号整数类型,都存在一个相应但不同的标准无符号整数类型:“unsigned char”,“unsigned short int”,“unsigned int”,“unsigned long int”,和“unsigned long long int”。同样,对于每种扩展有符号整数类型,都存在一个相应的扩展无符号整数类型。[...]

因为它不是一个有约束的函数,所以当传递一个非标准整数类型时,它仍然会被实例化并触发内部的可能静态断言。您可以通过检测T是否为标准整数类型来确定是否使用std::cmp_equal进行比较:

#include <utility>
#include <concepts>

template <typename T>
concept standard_integral = 
  std::integral<T> &&
  !std::same_as<T, char> &&
  !std::same_as<T, char8_t> &&
  !std::same_as<T, char16_t> &&
  !std::same_as<T, char32_t> &&
  !std::same_as<T, wchar_t> &&
  !std::same_as<T, bool>;

template <typename T, std::equality_comparable_with<T> U>
bool generic_equal(T t, U u) {
    if constexpr (standard_integral<T> && standard_integral<U>) {
        return std::cmp_equal(t, u);
    } else {
        return t == u;
    }
}

这是您提供的代码的翻译部分。

英文:

std::cmp_equal requires that the template parameter must be of standard integer types or extended integer types, which is defined in [basic.fundamental]:

> 1. There are five standard signed integer types: “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides at least as much storage as those preceding it in the list.
There may also be implementation-defined extended signed integer types. [...]
>
> 2. For each of the standard signed integer types, there exists a
> corresponding (but different) standard unsigned integer type:
> “unsigned char”, “unsigned short int”, “unsigned int”,
> “unsigned long int”, and “unsigned long long int”. Likewise, for
> each of the extended signed integer types, there exists a
> corresponding extended unsigned integer type. [...]

Since it is not a constrained function, it will still be instantiated and hit internal possible static assertions when passed a non-standard integer type. You can determine whether to use std::cmp_equal for comparison by detecting whether the T is a standard integer type:

#include &lt;utility&gt;
#include &lt;concepts&gt;

template &lt;typename T&gt;
concept standard_integral = 
  std::integral&lt;T&gt; &amp;&amp;
  !std::same_as&lt;T, char&gt; &amp;&amp;
  !std::same_as&lt;T, char8_t&gt; &amp;&amp;
  !std::same_as&lt;T, char16_t&gt; &amp;&amp;
  !std::same_as&lt;T, char32_t&gt; &amp;&amp;
  !std::same_as&lt;T, wchar_t&gt; &amp;&amp;
  !std::same_as&lt;T, bool&gt;;

template &lt;typename T, std::equality_comparable_with&lt;T&gt; U&gt;
bool generic_equal(T t, U u) {
    if constexpr (standard_integral&lt;T&gt; &amp;&amp; standard_integral&lt;U&gt;) {
        return std::cmp_equal(t, u);
    } else {
        return t == u;
    }
}

huangapple
  • 本文由 发表于 2023年6月9日 13:03:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76437366.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定