确定比较中文字的有效类型
Determine the effective type of a literal in a comparison
以下是我定义的宏的简化版本:
#define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while (false)
有效,但现在我想对评估的 a/b 值做额外的工作,并且希望每个值只评估一次。换句话说,类似于:
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
if (a_eval != b_eval) { \
/* Print a_eval/b_eval */ \
abort(); \
} \
} while (false)
但这打破了一些当前的用途,例如触发 -Wsign-compare
CHECK_EQ(some_unsigned, 1)
。我想要的是代替 auto
来确定比较的每一侧将转换为进行比较的类型。假设示例:
#define CHECK_EQ(a, b) \
do { \
using CmpType = CommonType<decltype(a), decltype(b)>::type; \ What goes here??
const CmpType a_eval = (a); \
const CmpType b_eval = (b); \
if (a_eval != b_eval) { \
/* Print a_eval & b_eval */ \
abort(); \
} \
} while (false)
我怀疑这也不完全正确,因为 decltype(1) 将是 int。有没有办法在不修改现有 CHECK_EQ
调用或抑制警告的情况下完成我想要的?
编辑:
关于什么应该和不应该 return 警告似乎有点混乱。当其中一个参数是正数 文字 时,不必要地使用 auto
returns 警告,这也是一个有效的无符号文字(但 auto
结果在 signed
) 中。换句话说,理想情况下,当且仅当 a == b
会发出警告时,CHECK_EQ(a, b)
才会发出警告。第二种最佳解决方案是允许混合类型,只要最终执行的比较是安全的 w.r.t。类型的签名。这似乎是使用 std::common_type
.
完成的
也许使用模板函数进行比较?
#include <iostream>
template<typename T1, typename T2>
static inline bool _NotEqual(const T1& a, const T2& b)
{
if (static_cast<T2>(static_cast<T1>(b)) == b) {
return a != static_cast<T1>(b);
} else {
return static_cast<T2>(a) != b;
}
}
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
if (_NotEqual(a_eval, b_eval)) { \
std::cerr << a_eval <<" != "<< b_eval << std::endl; \
abort(); \
} \
} while (false)
int main()
{
CHECK_EQ(1U, 1);
CHECK_EQ(2, 2.2);
}
假设 T1
和 T2
可以相互静态转换。
编辑:
关于~(0U) == -1
的担忧,如果不需要,那么我们可能应该不尝试过首先丢弃编译器警告。但是 ~(0U) == -1
并不是一件坏事,例如在很多情况下,标准库使用“-1”表示无符号 return.
(编辑最后有一个替代解决方案)
解决方案一(原创)
这从来没有正确地工作过,并且对于 CommonType 和 std:::common_type
都是不正确的。它过去和将来都是不正确的,因为 ~(0U) != -1
在这样的方案中评估为假(假设 2 的补码),您似乎期望它 return true
.
我建议使用模板函数:
// check if this is a simple int literal
// such as 1, 0, 6789, but not 1U and neither expressions like -1.
template <class T1, class T2>
bool is_same(const T1& a, const T2&b)
{
if (std::is_signed_v<T1> && !std::is_signed_v<T2>) {
// some compilers might warn about the following,
// in that case make it an "if constexpr" instead.
if (a < 0) return false;
}
if (!std::is_signed_v<T1> && std::is_signed_v<T2>) {
if (b < 0) return false;
}
std::common_type_t<T1, T2> a_common = a;
std::common_type_t<T1, T2> b_common = b;
return a == b;
}
那你可以这样写:
#define CHECK_EQ(a, b) \
do { \
if (!is_same(a_eval, b_eval)) { \
/* Print a_eval & b_eval */ \
abort(); \
} \
} while (false)
但如果我们做到了,为什么不直接使用模板函数呢?
template <typename T, typename U>
void check_eq(const T& a, const U& b)
{
if (!is_same(a,b))
{
/* print a and b */
abort();
}
}
注意:如果您使用的是 C++14 而不是 C++17,请将 std::is_signed_v<T>
替换为 std::is_signed<T>::value
。如果你有 C++11 而不是 C++14,那么将 std::common_type_t<T1, T2>
替换为 typename std::common_type<T1, T2>::type
.
解决方案 2
在对问题进行编辑后,文字 int
和任何其他类型的 int
值之间似乎存在区别。该代码应给出与 a == b
相同的警告,其中 a == 1
如果 a
未签名则不会发出警告。
为此我引入了宏 IS_INT_LITERAL:
template <std::size_t N>
constexpr bool is_int_str(const char (&str)[N])
{
// TODO: deal with 0x1Dbef hex literals
if (N < 2 || str[N-1] != '[=13=]') return false;
for (unsigned i=0 ; i < N-1 ; ++i)
// NOTE: This is only 99.9% portable. It assumes that '0'..'9' chars are consecutive.
//A more portable way would check (str[i] != '0 && str[i] != '1' ...)
if (str[i] < '0' || str[i] > '9') {
if (i == 0) return false;
// support 2ull , 1L, etc.
if (str[i] !='U' &&
str[i] != 'L' &&
str[i] != 'u' &&
str[i] != 'l' ) /* lower case L*/
{
return false;
}
}
return true;
}
#define IS_INT_LITERAL(x) is_int_str(#x)
然后可以在比较函数中使用该宏:
template <bool suppress_sign_warnings, class T1, class T2>
bool is_same(const T1 & a, const T2 & b)
{
if constexpr (suppress_sign_warnings) {
std::common_type_t<T1, T2> a_common = a, b_common = b;
return a_common == b_common;
} else {
return a == b;
}
}
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
constexpr bool any_literal = IS_INT_LITERAL(a) || IS_INT_LITERAL(b); \
if (! is_same<any_literal>(a_eval, b_eval)) { \
/* Print a_eval/b_eval */ \
abort(); \
} \
} while (false)
此操作没有警告:
CHECK_EQ(1, 1u); // like 1 == 1u
但这会产生警告:
void foo(int a, unsigned b = 1u)
{
CHECK_EQ(a, b); // like a == b
}
以下是我定义的宏的简化版本:
#define CHECK_EQ(a, b) do { if ((a) != (b)) abort(); } while (false)
有效,但现在我想对评估的 a/b 值做额外的工作,并且希望每个值只评估一次。换句话说,类似于:
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
if (a_eval != b_eval) { \
/* Print a_eval/b_eval */ \
abort(); \
} \
} while (false)
但这打破了一些当前的用途,例如触发 -Wsign-compare
CHECK_EQ(some_unsigned, 1)
。我想要的是代替 auto
来确定比较的每一侧将转换为进行比较的类型。假设示例:
#define CHECK_EQ(a, b) \
do { \
using CmpType = CommonType<decltype(a), decltype(b)>::type; \ What goes here??
const CmpType a_eval = (a); \
const CmpType b_eval = (b); \
if (a_eval != b_eval) { \
/* Print a_eval & b_eval */ \
abort(); \
} \
} while (false)
我怀疑这也不完全正确,因为 decltype(1) 将是 int。有没有办法在不修改现有 CHECK_EQ
调用或抑制警告的情况下完成我想要的?
编辑:
关于什么应该和不应该 return 警告似乎有点混乱。当其中一个参数是正数 文字 时,不必要地使用 auto
returns 警告,这也是一个有效的无符号文字(但 auto
结果在 signed
) 中。换句话说,理想情况下,当且仅当 a == b
会发出警告时,CHECK_EQ(a, b)
才会发出警告。第二种最佳解决方案是允许混合类型,只要最终执行的比较是安全的 w.r.t。类型的签名。这似乎是使用 std::common_type
.
也许使用模板函数进行比较?
#include <iostream>
template<typename T1, typename T2>
static inline bool _NotEqual(const T1& a, const T2& b)
{
if (static_cast<T2>(static_cast<T1>(b)) == b) {
return a != static_cast<T1>(b);
} else {
return static_cast<T2>(a) != b;
}
}
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
if (_NotEqual(a_eval, b_eval)) { \
std::cerr << a_eval <<" != "<< b_eval << std::endl; \
abort(); \
} \
} while (false)
int main()
{
CHECK_EQ(1U, 1);
CHECK_EQ(2, 2.2);
}
假设 T1
和 T2
可以相互静态转换。
编辑:
关于~(0U) == -1
的担忧,如果不需要,那么我们可能应该不尝试过首先丢弃编译器警告。但是 ~(0U) == -1
并不是一件坏事,例如在很多情况下,标准库使用“-1”表示无符号 return.
(编辑最后有一个替代解决方案)
解决方案一(原创)
这从来没有正确地工作过,并且对于 CommonType 和 std:::common_type
都是不正确的。它过去和将来都是不正确的,因为 ~(0U) != -1
在这样的方案中评估为假(假设 2 的补码),您似乎期望它 return true
.
我建议使用模板函数:
// check if this is a simple int literal
// such as 1, 0, 6789, but not 1U and neither expressions like -1.
template <class T1, class T2>
bool is_same(const T1& a, const T2&b)
{
if (std::is_signed_v<T1> && !std::is_signed_v<T2>) {
// some compilers might warn about the following,
// in that case make it an "if constexpr" instead.
if (a < 0) return false;
}
if (!std::is_signed_v<T1> && std::is_signed_v<T2>) {
if (b < 0) return false;
}
std::common_type_t<T1, T2> a_common = a;
std::common_type_t<T1, T2> b_common = b;
return a == b;
}
那你可以这样写:
#define CHECK_EQ(a, b) \
do { \
if (!is_same(a_eval, b_eval)) { \
/* Print a_eval & b_eval */ \
abort(); \
} \
} while (false)
但如果我们做到了,为什么不直接使用模板函数呢?
template <typename T, typename U>
void check_eq(const T& a, const U& b)
{
if (!is_same(a,b))
{
/* print a and b */
abort();
}
}
注意:如果您使用的是 C++14 而不是 C++17,请将 std::is_signed_v<T>
替换为 std::is_signed<T>::value
。如果你有 C++11 而不是 C++14,那么将 std::common_type_t<T1, T2>
替换为 typename std::common_type<T1, T2>::type
.
解决方案 2
在对问题进行编辑后,文字 int
和任何其他类型的 int
值之间似乎存在区别。该代码应给出与 a == b
相同的警告,其中 a == 1
如果 a
未签名则不会发出警告。
为此我引入了宏 IS_INT_LITERAL:
template <std::size_t N>
constexpr bool is_int_str(const char (&str)[N])
{
// TODO: deal with 0x1Dbef hex literals
if (N < 2 || str[N-1] != '[=13=]') return false;
for (unsigned i=0 ; i < N-1 ; ++i)
// NOTE: This is only 99.9% portable. It assumes that '0'..'9' chars are consecutive.
//A more portable way would check (str[i] != '0 && str[i] != '1' ...)
if (str[i] < '0' || str[i] > '9') {
if (i == 0) return false;
// support 2ull , 1L, etc.
if (str[i] !='U' &&
str[i] != 'L' &&
str[i] != 'u' &&
str[i] != 'l' ) /* lower case L*/
{
return false;
}
}
return true;
}
#define IS_INT_LITERAL(x) is_int_str(#x)
然后可以在比较函数中使用该宏:
template <bool suppress_sign_warnings, class T1, class T2>
bool is_same(const T1 & a, const T2 & b)
{
if constexpr (suppress_sign_warnings) {
std::common_type_t<T1, T2> a_common = a, b_common = b;
return a_common == b_common;
} else {
return a == b;
}
}
#define CHECK_EQ(a, b) \
do { \
const auto a_eval = (a); \
const auto b_eval = (b); \
constexpr bool any_literal = IS_INT_LITERAL(a) || IS_INT_LITERAL(b); \
if (! is_same<any_literal>(a_eval, b_eval)) { \
/* Print a_eval/b_eval */ \
abort(); \
} \
} while (false)
此操作没有警告:
CHECK_EQ(1, 1u); // like 1 == 1u
但这会产生警告:
void foo(int a, unsigned b = 1u)
{
CHECK_EQ(a, b); // like a == b
}