如何在 C++ 中打印编译时计算的结果?
How to print result of a compile-time calculation in C++?
我写了几个 constexpr 函数并在 static_asserts 中使用它们来控制一些资源限制。但我不仅想强制执行编译时谓词,还想查看在正常编译过程中或至少在断言失败时计算的实际值。
有一些方法可以在编译期间打印字符串消息,但是打印 constexpr 计算的结果呢?
这里有一些代码利用 gcc 的诊断消息在断言消息后打印感兴趣的值。要查找感兴趣的值,您只需在错误字符串中搜索 T x =
:
#include <string>
template <class T, T x, class F>
void transparent(F f) { f(); }
template <bool B>
constexpr void my_assert() {
static_assert(B, "oh no");
}
template <int X>
void f() {
transparent<int, X+7>([]{
transparent<long, X*X*X>([]{
my_assert<X+10==-89>(); });});
}
int main() {
// f<3>();
f<4>();
// f<-99>();
}
这是我遇到的错误:
g++ h.cpp -std=c++11
h.cpp: In instantiation of ‘constexpr void my_assert() [with bool B = false]’:
h.cpp:16:34: required from ‘f() [with int X = 4]::__lambda0::__lambda1’
h.cpp:15:35: required from ‘struct f() [with int X = 4]::__lambda0::__lambda1’
h.cpp:16:38: required from ‘f() [with int X = 4]::__lambda0’
h.cpp:14:28: required from ‘struct f() [with int X = 4]::__lambda0’
h.cpp:16:41: required from ‘void f() [with int X = 4]’
h.cpp:21:10: required from here
h.cpp:9:5: error: static assertion failed: oh no
static_assert(B, "oh no");
^
h.cpp:4:6: error: ‘void transparent(F) [with T = long int; <b>T x = 64l</b>; F = f() [with int X = 4]::__lambda0::__lambda1]’, declared using local type ‘f() [with int X = 4]::__lambda0::__lambda1’, is used but never defined [-fpermissive]
void transparent(F f) { f(); }
^
h.cpp:4:6: error: ‘void transparent(F) [with T = int; <b>T x = 11</b>; F = f() [with int X = 4]::__lambda0]’, declared using local type ‘f() [with int X = 4]::__lambda0’, is used but never defined [-fpermissive]
注意粗体部分
我的 VC++ 代码在编译期间打印任意数量的编译时间常量(例如 sizeof 结构)的值并继续编译而不会出错:
// cpptest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
//#define VALUE_TO_STRING2(x) #x
//#define VALUE_TO_STRING(x) VALUE_TO_STRING2(x)
#define TO_STRING(x) #x
#define FUNC_TEMPLATE_MSG(x,y) "[" x "]""["TO_STRING(y)"]"
template<unsigned int N,unsigned int M>
int printN()
{
#pragma message(FUNC_TEMPLATE_MSG(__FUNCSIG__ ,1))
return 0;
};
struct X {
char a[20];
int b;
};
struct Y {
char a[210];
int b;
};
int _tmain(int argc, _TCHAR* argv[])
{
printN<sizeof(X),__COUNTER__>();
printN<sizeof(Y),__COUNTER__>();
//..as many compile time constants as you like
}
VC++2010 生成的示例输出。目标值是 VC++ 奇怪地选择输出为十六进制值的第一个函数模板参数值(示例中的 0x18 和 0xd8)!
1>------ Build started: Project: cpptest, Configuration: Release Win32 ------
1> cpptest.cpp
1> [int __cdecl printN<0x18,0x0>(void)][1]
1> [int __cdecl printN<0xd8,0x1>(void)][1]
1> Generating code
1> Finished generating code
1> cpptest.vcxproj -> c:\work\cpptest\Release\cpptest.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
这里使用的主要技术是在每个函数模板实例化期间,#pragma message()
指令被调用一次。 Microsoft 特定的宏 __FUNCSIG__
可以显示包含函数的签名,从而显示在函数模板的每个特定实例化中使用的整数值。将 COUNTER
作为第二个模板参数是为了确保相同值的 2 个整数仍然被认为是不同的。 #pragma
指令中的 1 在这里没有用,但可以用作标识符,以防我们有多个这样的指令并且输出 window 充满了混乱的消息。
这是基于:
如果您需要在没有 static_assert()
的情况下打印 constexpr 值,这适用于带有 -Wunused
标志的 GCC 编译器:
// before c++17:
template <typename T, T val>
constexpr void static_print() {
#if !defined(__GNUC__) || defined(__clang__)
int static_print_is_implemented_only_for_gcc = 0;
#else
int unused = 0;
#endif
};
// for c++17 and higher:
template <auto val>
constexpr void static_print() {
#if !defined(__GNUC__) || defined(__clang__)
int static_print_is_implemented_only_for_gcc = 0;
#else
int unused = 0;
#endif
};
int main() {
constexpr int i = 13;
// for c++17 and higher:
static_print<i>();
// before c++17:
static_print<int, i>();
}
输出:
$ g++ -std=c++17 main.cpp -Wall && ./a
main.cpp: In instantiation of 'constexpr void static_print() [with auto val = 13]':
main.cpp:23:21: required from here
main.cpp:17:7: warning: unused variable 'unused' [-Wunused-variable]
int unused = 0;
^~~~~~
main.cpp: In instantiation of 'constexpr void static_print() [with T = int; T val = 13]':
main.cpp:24:26: required from here
main.cpp:8:7: warning: unused variable 'unused' [-Wunused-variable]
int unused = 0;
^~~~~~
重要的部分是val = 13
。
在 https://godbolt.org/z/Cdb-At
在线玩这个例子
遗憾的是,Clang 编译器不包括警告消息的回溯,因此它不输出值。
最近 GCC compilers (g++
, in 2020 GCC 9 at least) on at least Linux systems (and probably others) you could develop your GCC plugin 将您的 __builtin_compile_time_print
添加到现有的 GCC 内置函数中。或者添加您自己的 #pragma
或 _Pragma
具有类似的副作用。
我开始工作了(使用来自 CHARIOT) on the Clips-rules-gcc 项目的欧洲 H2020 资金。可能在 2 或 3 个月内你可以使用它来做你想做的事情。请继续关注。
另请参阅我过时的 GCC MELT 项目,它可以让您使用一些过时的 4.6 GCC 编译器做您想做的事。
当然你可以修补 Clang/GCC 来做类似的事情。
@je4d () 有一个回答,
它适用于 godbolt 上的 msvc、gcc、clang 和许多编译器,除了古老的 gcc 4.1.2
#define strcat_(x, y) x ## y
#define strcat(x, y) strcat_(x, y)
#define PRINT_VALUE(x) \
template <int> \
struct strcat(strcat(value_of_, x), _is); \
static_assert(strcat(strcat(value_of_, x), _is)<x>::x, "");
#line 4242
constexpr int PI_INT = __LINE__; /*set the value*/
PRINT_VALUE(PI_INT) /*print it*/
它打印错误消息中的值:
:4244:1: error: incomplete type 'value_of_PI_INT_is<4242>'
used in nested name specifier
我写了几个 constexpr 函数并在 static_asserts 中使用它们来控制一些资源限制。但我不仅想强制执行编译时谓词,还想查看在正常编译过程中或至少在断言失败时计算的实际值。
有一些方法可以在编译期间打印字符串消息,但是打印 constexpr 计算的结果呢?
这里有一些代码利用 gcc 的诊断消息在断言消息后打印感兴趣的值。要查找感兴趣的值,您只需在错误字符串中搜索 T x =
:
#include <string>
template <class T, T x, class F>
void transparent(F f) { f(); }
template <bool B>
constexpr void my_assert() {
static_assert(B, "oh no");
}
template <int X>
void f() {
transparent<int, X+7>([]{
transparent<long, X*X*X>([]{
my_assert<X+10==-89>(); });});
}
int main() {
// f<3>();
f<4>();
// f<-99>();
}
这是我遇到的错误:
g++ h.cpp -std=c++11
h.cpp: In instantiation of ‘constexpr void my_assert() [with bool B = false]’:
h.cpp:16:34: required from ‘f() [with int X = 4]::__lambda0::__lambda1’
h.cpp:15:35: required from ‘struct f() [with int X = 4]::__lambda0::__lambda1’
h.cpp:16:38: required from ‘f() [with int X = 4]::__lambda0’
h.cpp:14:28: required from ‘struct f() [with int X = 4]::__lambda0’
h.cpp:16:41: required from ‘void f() [with int X = 4]’
h.cpp:21:10: required from here
h.cpp:9:5: error: static assertion failed: oh no
static_assert(B, "oh no");
^
h.cpp:4:6: error: ‘void transparent(F) [with T = long int; <b>T x = 64l</b>; F = f() [with int X = 4]::__lambda0::__lambda1]’, declared using local type ‘f() [with int X = 4]::__lambda0::__lambda1’, is used but never defined [-fpermissive]
void transparent(F f) { f(); }
^
h.cpp:4:6: error: ‘void transparent(F) [with T = int; <b>T x = 11</b>; F = f() [with int X = 4]::__lambda0]’, declared using local type ‘f() [with int X = 4]::__lambda0’, is used but never defined [-fpermissive]
注意粗体部分
我的 VC++ 代码在编译期间打印任意数量的编译时间常量(例如 sizeof 结构)的值并继续编译而不会出错:
// cpptest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
//#define VALUE_TO_STRING2(x) #x
//#define VALUE_TO_STRING(x) VALUE_TO_STRING2(x)
#define TO_STRING(x) #x
#define FUNC_TEMPLATE_MSG(x,y) "[" x "]""["TO_STRING(y)"]"
template<unsigned int N,unsigned int M>
int printN()
{
#pragma message(FUNC_TEMPLATE_MSG(__FUNCSIG__ ,1))
return 0;
};
struct X {
char a[20];
int b;
};
struct Y {
char a[210];
int b;
};
int _tmain(int argc, _TCHAR* argv[])
{
printN<sizeof(X),__COUNTER__>();
printN<sizeof(Y),__COUNTER__>();
//..as many compile time constants as you like
}
VC++2010 生成的示例输出。目标值是 VC++ 奇怪地选择输出为十六进制值的第一个函数模板参数值(示例中的 0x18 和 0xd8)!
1>------ Build started: Project: cpptest, Configuration: Release Win32 ------
1> cpptest.cpp
1> [int __cdecl printN<0x18,0x0>(void)][1]
1> [int __cdecl printN<0xd8,0x1>(void)][1]
1> Generating code
1> Finished generating code
1> cpptest.vcxproj -> c:\work\cpptest\Release\cpptest.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
这里使用的主要技术是在每个函数模板实例化期间,#pragma message()
指令被调用一次。 Microsoft 特定的宏 __FUNCSIG__
可以显示包含函数的签名,从而显示在函数模板的每个特定实例化中使用的整数值。将 COUNTER
作为第二个模板参数是为了确保相同值的 2 个整数仍然被认为是不同的。 #pragma
指令中的 1 在这里没有用,但可以用作标识符,以防我们有多个这样的指令并且输出 window 充满了混乱的消息。
这是基于
如果您需要在没有 static_assert()
的情况下打印 constexpr 值,这适用于带有 -Wunused
标志的 GCC 编译器:
// before c++17:
template <typename T, T val>
constexpr void static_print() {
#if !defined(__GNUC__) || defined(__clang__)
int static_print_is_implemented_only_for_gcc = 0;
#else
int unused = 0;
#endif
};
// for c++17 and higher:
template <auto val>
constexpr void static_print() {
#if !defined(__GNUC__) || defined(__clang__)
int static_print_is_implemented_only_for_gcc = 0;
#else
int unused = 0;
#endif
};
int main() {
constexpr int i = 13;
// for c++17 and higher:
static_print<i>();
// before c++17:
static_print<int, i>();
}
输出:
$ g++ -std=c++17 main.cpp -Wall && ./a
main.cpp: In instantiation of 'constexpr void static_print() [with auto val = 13]':
main.cpp:23:21: required from here
main.cpp:17:7: warning: unused variable 'unused' [-Wunused-variable]
int unused = 0;
^~~~~~
main.cpp: In instantiation of 'constexpr void static_print() [with T = int; T val = 13]':
main.cpp:24:26: required from here
main.cpp:8:7: warning: unused variable 'unused' [-Wunused-variable]
int unused = 0;
^~~~~~
重要的部分是val = 13
。
在 https://godbolt.org/z/Cdb-At
在线玩这个例子遗憾的是,Clang 编译器不包括警告消息的回溯,因此它不输出值。
最近 GCC compilers (g++
, in 2020 GCC 9 at least) on at least Linux systems (and probably others) you could develop your GCC plugin 将您的 __builtin_compile_time_print
添加到现有的 GCC 内置函数中。或者添加您自己的 #pragma
或 _Pragma
具有类似的副作用。
我开始工作了(使用来自 CHARIOT) on the Clips-rules-gcc 项目的欧洲 H2020 资金。可能在 2 或 3 个月内你可以使用它来做你想做的事情。请继续关注。
另请参阅我过时的 GCC MELT 项目,它可以让您使用一些过时的 4.6 GCC 编译器做您想做的事。
当然你可以修补 Clang/GCC 来做类似的事情。
@je4d () 有一个回答,
它适用于 godbolt 上的 msvc、gcc、clang 和许多编译器,除了古老的 gcc 4.1.2
#define strcat_(x, y) x ## y
#define strcat(x, y) strcat_(x, y)
#define PRINT_VALUE(x) \
template <int> \
struct strcat(strcat(value_of_, x), _is); \
static_assert(strcat(strcat(value_of_, x), _is)<x>::x, "");
#line 4242
constexpr int PI_INT = __LINE__; /*set the value*/
PRINT_VALUE(PI_INT) /*print it*/
它打印错误消息中的值:
:4244:1: error: incomplete type 'value_of_PI_INT_is<4242>' used in nested name specifier