【问题标题】:How to print result of a compile-time calculation in C++?如何在 C++ 中打印编译时计算的结果?
【发布时间】:2015-05-05 07:55:35
【问题描述】:

我写了几个 constexpr 函数并在 static_asserts 中使用它们来控制一些资源限制。但我不仅想强制执行编译时谓词,还想查看在正常编译过程中或至少在断言失败时计算的实际值。

在编译期间有打印字符串消息的方法,但是打印 constexpr 计算的结果呢?

【问题讨论】:

  • Shame static_assert 的消息参数不能做到这一点。
  • 您可以通过在条件失败时强制不完整类型来利用常规编译器诊断,这将导致模板参数在编译器输出中可见,like here
  • 我可以用 VC++ 做到这一点(见我的回答)。也许有人可以尝试看看是否可以在 gcc 中使用相同的技术

标签: c++ c++11 gcc constexpr static-assert


【解决方案1】:

这是一些利用 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]

注意加粗部分

【讨论】:

  • 行得通,谢谢!知道如何发出警告消息以便继续编译吗?
【解决方案2】:

这是基于solution posted by @tohava:

如果您需要打印没有 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 10.2 上,我必须稍微调整虚拟 static_print 函数的签名以使其返回(虚拟)int。否则我会遇到另一个编译错误:specializing member '::static_print&lt;...&gt;' requires 'template&lt;&gt;' syntax
【解决方案3】:

我的 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 作为第二个模板参数是为了确保两个相同值的整数仍然被认为是不同的。 #pragma 指令中的 1 在这里没有用,但可以用作标识符,以防我们有超过 1 个这样的指令并且输出窗口充满杂乱的消息。

【讨论】:

    【解决方案4】:

    @je4d (https://stackoverflow.com/a/13465643/3353857) 给出了答案,
    它适用于 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: 错误:不完整的类型'value_of_PI_INT_is4242>' 用于嵌套名称说明符

    【讨论】:

      【解决方案5】:

      至少在 Linux 系统(可能还有其他系统)上使用最新的 GCC 编译器(g++,至少在 2020 年 GCC 9),您可以开发您的 GCC plugin,将您的 __builtin_compile_time_print 添加到现有的 GCC 内置职能。或者添加您自己的 #pragma_Pragma 具有类似的副作用。

      我开始在 Clips-rules-gcc 项目上工作(获得来自 CHARIOT 的欧洲 H2020 资助)。可能在 2 或 3 个月后,您就可以使用它来做您想做的事。请继续关注。

      另请参阅我过时的 GCC MELT 项目,它可以让您使用一些过时的 4.6 GCC 编译器做您想做的事情。

      当然你可以修补 Clang/GCC 来做类似的事情。

      【讨论】:

      • 这个项目有什么更新吗?
      猜你喜欢
      • 2014-01-25
      • 2011-06-26
      • 2018-10-25
      • 2014-06-03
      • 2019-01-02
      • 1970-01-01
      • 2012-10-09
      • 2011-01-03
      • 2017-03-04
      相关资源
      最近更新 更多