借助static_assert改进诊断

Naw*_*waz 9 c++ templates diagnostics static-assert c++11

在模板编程中,static_assert帮助程序员检查模板参数上的约束,并在违反约束时生成人类可读的错误消息.

考虑一下这段代码

template<typename T>
void f(T)
{
    static_assert(T(), "first requirement failed to meet.");

    static_assert(T::value, "second requirement failed to meet.");    

    T t = 10; //even this may generate error!
}
Run Code Online (Sandbox Code Playgroud)

我的想法是:如果第一个static_assert失败,这意味着一些要求T不符合,因此编译应该停止,生成第一个错误消息 - 因为继续编译只是为了生成越来越多的没有多大意义错误消息,其中大多数通常指向单个约束违规.数以百计的错误信息,而不是仅仅一个,看起来很吓人的屏幕上-我甚至可以说,这违背十分目的static_assert在一定程度上.

例如,如果我将上述函数模板调用为:

f(std::false_type{});
Run Code Online (Sandbox Code Playgroud)

GCC 4.8生成以下内容:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:16:24:   required from here
main.cpp:7:5: error: static assertion failed: first requirement failed to meet.
     static_assert(T(), "first requirement failed to meet.");
     ^
main.cpp:9:5: error: static assertion failed: second requirement failed to meet.
     static_assert(T::value, "second requirement failed to meet.");    
     ^
main.cpp:11:11: error: conversion from 'int' to non-scalar type 'std::integral_constant<bool, false>' requested
     T t = 10; //even this may generate error!
Run Code Online (Sandbox Code Playgroud)

正如您所看到的(在线),这是错误的.如果第一个static_assert失败,如果编译继续,很可能其余代码失败,那么为什么继续编译呢?在模板编程中,我确信很多程序员不需要这样的级联错误消息!

我试图通过函数分成多个函数来解决这个问题,每个函数只检查一个约束,如下所示:

template<typename T>
void f_impl(T); //forward declaration

template<typename T>
void f(T)
{
    static_assert(T(), "first requirement failed to meet.");
    f_impl(T());
}

template<typename T>
void f_impl(T)
{
    static_assert(T::value, "second requirement failed to meet.");     
    T t = 10;
}  

f(std::false_type{}); //call
Run Code Online (Sandbox Code Playgroud)

现在生成这个:

main.cpp: In instantiation of 'void f(T) [with T = std::integral_constant<bool, false>]':
main.cpp:24:24:   required from here
main.cpp:10:5: error: static assertion failed: first requirement failed to meet.
     static_assert(T(), "first requirement failed to meet.");
     ^
Run Code Online (Sandbox Code Playgroud)

这是一个很大的改进 - 只有一个错误信息更容易阅读和理解(见在线).

我的问题是,

  • 为什么编译不会停在第一个static_assert
  • 由于功能模板的拆分和每个function_impl中的一个约束检查,只有 GCC和clang 仍然会 产生很多错误,有没有办法以更一致的方式改进诊断 - 哪些适用于所有编译器?

Cas*_*eri 4

我同意 David Rodr\xc3\xadguez 的观点 - dribeas 并为编译器编写者辩护,考虑这个例子:

\n\n
#include <type_traits>\n\nclass A {};\n\n// I want the nice error message below in several functions.\n// Instead of repeating myself, let\'s put it in a function.\ntemplate <typename U>\nvoid check() {\n    static_assert(std::is_convertible<U*, const volatile A*>::value,\n        "U doesn\'t derive publicly from A "\n        "(did you forget to include it\'s header file?)");\n}\n\ntemplate <typename U>\nvoid f(U* u) {\n    // check legality (with a nice error message)\n    check<U>();\n    // before trying a failing initialization:\n    A* p = u;\n}\n\nclass B; // I forget to include "B.h"\n\nint main() {\n    B* b = nullptr;\n    f(b);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当实例化f<B>启动时,编译器(或编译器编写者)可能会想:“嗯……我需要实例化check<U>,但人们总是抱怨编译模板太慢了。所以我会继续下去,也许有什么东西下面错误,我不需要实例化check。”

\n\n

我相信上面的推理是有道理的。(请注意,我不是编译器编写者,所以我只是在这里推测)。

\n\n

GCC 4.8和VS2010都继续编译f<B>,推迟实例化check<B>以供稍后使用。然后他们找到失败的初始化并提供自己的错误消息。VS2010 立即停止,我没有收到漂亮的错误消息!GCC 继续前进并产生我想要的消息(但只有在它自己的消息之后)。

\n\n

元编程对于程序员和编译器来说都是很棘手的。有很大static_assert帮助,但它不是万能药。

\n