如何使is_pod<T>测试在编译期间执行而不是执行

How can I make is_pod<T> tests be performed during compilation and not execution?

本文关键字:执行 编译 is 何使 pod 测试      更新时间:2023-10-16

这可能是一个简单的问题,我根本不掌握C++11模板。

我有一个通用向量类,出于性能原因(非常具体的代码(不std::vector<T>

我观察到,检查T是否是 POD,并在是时执行特殊计算,比不检查更有效:

void vec<T>::clear() {
  if (!std::is_pod<T>::value) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
  }
  size = 0;
}

在这里,我不为每个项目调用 T 的析构函数(size可能非常巨大(,并且性能确实得到了提升。但是一旦模板被编译,测试if (!std::is_pod<T>::value)就没有用了:而不是被编译成:

void vec<int>::clear() {
  if (false) {
    for (int i = 0; i < size; i++) {
       data[i].~int();
    }
  }
  size = 0;
}

我希望它被编译为:

void vec<int>::clear() {
  size = 0;
}

编译器是否足够"聪明",可以跳过if (false)块或if (true)测试?我必须以不同的方式编写该代码吗?

编译器是否足够"聪明",可以跳过 if (假( 块或 if (真(测试?

是的,当然。死代码消除是一种常规执行的微不足道的优化。它的存在对于使许多调试库高效工作也至关重要(= 在发布模式下没有运行时开销(。

但我可能仍然会重写它,让读者看到这是一个编译时的区别,通过重载基于is_pod的函数:

void vec<T>::do_clear(std::true_type) { }
void vec<T>::do_clear(std::false_type) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
}
void vec<T>::clear() {
    do_clear(std::is_trivially_destructible<T>());
    size = 0;
}

在上面的代码中,我使用 is_trivially_destructible 而不是 is_pod 使代码更加不言自明,正如 Nicol 在注释中建议的那样。此技术通常用于标准库实现和其他库。这称为标记调度

有一种称为伪析构函数的语言功能,它是专门为您想要做的事情而设计的。 基本上给定一个类型模板参数 T,你可以语法上为它调用析构函数,如果在实例化时 T 是标量类型(因为例如它是像int这样的基本类型(,它将编译并生成一个 no-op 在其位置。

对于其余非标量的 POD 类型,它们具有简单的析构函数,因此同样会生成无操作。

即使是最低优化设置的任何生产编译器都会避免无操作的循环。 所以你可以安全地写:

void vec<T>::clear() { 
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
    size = 0;
}

基本上,您正在尝试解决编译器已经为您处理的假想的性能问题。

消除死代码是一种常见的优化。

但是,如果您根本不信任编译器进行任何优化,则可以创建一个静态 if template库。

跳到妙语,如果你不想阅读一堆非常可怕的黑客。

#include <utility>
#include <type_traits>
template<bool b>
struct static_if_t {
  static_if_t( static_if_t const& ) = default;
  static_if_t() = default;
  static_if_t( static_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};
template<bool dead>
struct static_if_branch {};
template<bool b>
struct static_else_if_t {
  static_else_if_t( static_else_if_t const& ) = default;
  static_else_if_t() = default;
  static_else_if_t( static_else_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};
template<bool b>
static_if_t<b> static_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
template<bool b>
static_else_if_t<b> static_else_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
static auto static_else = static_else_if<true>;
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<true> operator*( static_if_t<true>, Lambda&& closure )
{
  std::forward<Lambda>(closure)();
  return {};
}
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<false> operator*( static_if_t<false>, Lambda&& /*closure*/ )
{
  return {};
}
template<typename Unused>
static_if_branch<true> operator*( static_if_branch<true>, Unused&& ) {
  return {};
}
static_if_t< true > operator*( static_if_branch<false>, static_else_if_t<true> ) {
  return {};
}
static_if_t< false > operator*( static_if_branch<false>, static_else_if_t<false> ) {
  return {};
}

这是重点:

#include <iostream>
int main() {
  static_if<true>* [&]{
    std::cout << "hellon";
  } *static_else* [&]{
    std::cout << "doomn";
  };
  static_if<false>* [&]{
    std::cout << "doom then";
  } *static_else* [&]{
    std::cout << "worldn";
  };
  static_if<false>* [&]{
    std::cout << "fellon";
  } *static_else_if<false>* [&]{
    std::cout << "yellown";
  } *static_else_if<false>* [&]{
    std::cout << "hehen";
  };
  static_if( std::is_same<int, int>() )* [&]{
    std::cout << "int is intn";
  };
  static_if( std::is_same<double, double>() )* [&]{
    std::cout << "double is doublen";
  } *static_else_if( std::is_same<int, double>() )* [&]{
    std::cout << "int is doublen";
  } *static_else* [&]{
    std::cout << "sky is not bluen";
  };
}

但是你为什么要这样做呢? 现场示例

(请注意,上面static_if有两种语法 - 一种static_if<compile time boolean expression>,另一种static_if( std::is_whatever<blah>() )(。

现在,虽然上述内容完全是疯狂的,但上述技术可以让您编写一个编译时三位一体运算符,该运算符允许根据选择的分支使用不同的类型。 这很整洁。

即,像这样:

auto result = trinary<std::is_same<A,B>::value>% 7 | 3.14;

如果AB是相同的类型,则result的类型将int,如果它们不同,则double。 甚至:

auto result = meta_trinary<std::is_same<A,B>::value>% [&]{return 7;} | [&]{return 3.14;};

如果您愿意,允许有条件地计算整个代码块,并存储返回值的条件类型。