11

假设我有一个模板函数,它接受一个整数和一个对类型 T 实例的 const 引用。现在根据整数,只有一些 T 是可接受的,否则在运行时会引发异常。

如果此函数的所有使用都使用常量整数,则可以使 int 成为模板参数并使用静态断言来检查它是否可以接受。所以不是func(1,c)一个人会使用func<1>(c)并且会获得编译时类型检查。有什么方法可以编写func(1,c)并保持编译时检查,同时还可以编写func(i,c)和使用动态断言?目标是使其对开发人员透明。添加这种安全性而无需为诸如编译时常量之类的事情打扰开发人员就太好了。他们可能只记得func(1,c)总是有效并使用它,避免检查。

如何尽可能使用静态断言和动态断言定义函数?


以下代码显示了Ivan Shcherbakov的 GCC 解决方案:

#include <iostream>
#include <cassert>

template<typename T>
void __attribute__((always_inline)) func(const int& i, const T& t);

void compile_time_error_() __attribute__((__error__ ("assertion failed")));

template<>
  void __attribute__((always_inline))
  func(const int& i, const float& t)
{
    do {
        if (i != 0) {
            if (__builtin_constant_p(i)) compile_time_error_();
            std::cerr << "assertion xzy failed" << std::endl;
            exit(1);
        }
    } while (0);
    func_impl<float>(i,t);
}

这将只允许 i=0 和 T=float 的组合。对于其他组合,一个好方法是创建一个宏,该宏产生template<> func(const int& i, const T& t)替换 T 和 i != 0 的代码。

4

2 回答 2

8

好吧,如果您使用的是 GCC,则可以使用肮脏的 hack,但它仅在启用函数内联(-O1 或更多)时才有效:

void my_error() __attribute__((__error__ ("Your message here")));

template <typename T1, typename T2> struct compare_types 
{
    enum {Equals = 0};
};

template <typename T1> struct compare_types<T1,T1> 
{
    enum {Equals = 1};
};

template <typename Type> __attribute__((always_inline)) void func(int a, Type &x)
{
    if (__builtin_constant_p(a))
    {
        if (a == 1 && compare_types<Type,char>::Equals)
            my_error();
    }
}

在这种情况下 whena == 1Typeis char,你会得到一个错误。这是一个将触发它的示例:

int main()
{
    char x;
    func(1, x);
    return 0;
}

请注意,此示例严重依赖 gcc 特定的__builtin_constant_p()函数,并且不适用于其他编译器!

于 2012-07-11T21:47:48.580 回答
2

让我解释一下问题,以便在我的回答中更准确:

运行时断言有时会在编译时报告错误。

Gcc 可以,但仅在某些优化级别上,并且错误消息非常不具信息性。Clang 本身不能(没有错误属性),但不要忘记 clang分析器。Analyzer 可以报告一些运行时错误,例如取消引用空指针。

所以这里有一个“智能”运行时断言的想法和简单的测试:

#include <cstdlib> // std::abort

#if !defined(__clang__) && defined(__GNUC__)
// clang emulates gcc
# define GCC_COMPILER 1
#else
# define GCC_COMPILER 0
#endif

#if GCC_COMPILER
void assertion_failed_message() __attribute__((__error__("assertion failed")));
#endif

inline void smart_assert(bool condition) {
#if GCC_COMPILER
  // gcc is very 'sensitive', it must be first code lines in this function
  if (__builtin_constant_p(condition) && !condition) {
    assertion_failed_message();
  }
#endif

  if (condition) {
    // Ok
    return;
  }

#if defined(__clang_analyzer__)
  enum {
    ASSERTION_FAILED = 0xdeadfull
  };
  int *a = nullptr;
  *a = ASSERTION_FAILED;
#endif

  // Implement some standart error, like abort
  std::abort();
}

void test_condition_2(bool condition) {
  smart_assert(condition);
}

void test_condition_1(bool condition) {
  test_condition_2(condition);
}

void test_condition_0(bool condition) {
  test_condition_1(condition);
}

int main() {
  test_condition_0(0==1);
  return EXIT_SUCCESS;
}

Gcc 在 O2 优化级别报错,很好。但是报告消息在主函数中,并且不要留下任何关于 test_condition_{0,1,2} 的信息。

Clang 分析器报告错误,如果您使用 Xcode,您可以看到从 main 到 smart_assert 的所有路径: clang_analyzer_report

PS clang 分析器并不完美,所以如果你尝试 test_condition_0(argc),不会报错(真正的运行时检查),但如果你尝试 test_condition_0(argc==1),则会报错。

于 2013-08-14T11:40:27.617 回答