内联递归函数

And*_*and 11 c++ recursion inlining micro-optimization compiler-optimization

当我尝试编译此代码时:

#include <iostream>
#include <limits.h>

// End recursive template-expansion of function select below.
template <typename Type>
static inline constexpr Type select(unsigned index)
{ return Type(); }

// Select one of the items passed to it.
// e.g. select(0, a, b, c) = a; select(1, a, b, c) = b; etc.
template <typename Type, typename... Params>
[[gnu::always_inline]]
static inline constexpr Type select(unsigned index, Type value, Params... values)
{ return index == 0 ? value : select<Type>(index - 1, values...); }

template <typename Type>
[[gnu::always_inline]]
static inline constexpr Type reflect_mask_helper_1(Type mask, Type shift, Type value)
{ return ((value & mask) >> shift) | ((value << shift) & mask); }

template <typename Type>
[[gnu::always_inline]]
static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
{
  return i == 0
    ? value
    : reflect_mask_helper_0(
        i - 1,
        reflect_mask_helper_1<Type>(
          select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
                        0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
          1 << (i - 1),
          value));
}

template <typename Type>
[[gnu::flatten]]
static inline constexpr Type reflect_mask(Type value)
{ return reflect_mask_helper_0(__builtin_ctz(sizeof(Type) * CHAR_BIT), value); }

int main(void) {
  for (int i = 0; i < 65536; i++) {
    std::cout << reflect_mask<uint16_t>(i) << std::endl;
  }
}
Run Code Online (Sandbox Code Playgroud)

gcc给我一个错误,指出该函数reflect_mask_helper_0无法内联,因为它是递归的。但是,该函数select也是递归的,但是gcc内联了它而没有抱怨。我在这里想念什么?

(我需要它是递归的,因为constexpr函数不能在C ++ 11下包含循环。)

错误信息:

% g++ test.cpp -O3 -march=native -c
test.cpp: In function ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’:
test.cpp:23:30: error: inlining failed in call to always_inline ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’: recursive inlining
   23 | static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
      |                              ^~~~~~~~~~~~~~~~~~~~~
test.cpp:27:28: note: called from here
   27 |     : reflect_mask_helper_0(
      |       ~~~~~~~~~~~~~~~~~~~~~^
   28 |         i - 1,
      |         ~~~~~~              
   29 |         reflect_mask_helper_1<Type>(
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |           select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                         0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   32 |           1 << (i - 1),
      |           ~~~~~~~~~~~~~     
   33 |           value));
      |           ~~~~~~~           
test.cpp: In function ‘int main()’:
test.cpp:23:30: error: inlining failed in call to always_inline ‘constexpr Type reflect_mask_helper_0(unsigned int, Type) [with Type = short unsigned int]’: recursive inlining
   23 | static inline constexpr Type reflect_mask_helper_0(unsigned i, Type value)
      |                              ^~~~~~~~~~~~~~~~~~~~~
test.cpp:27:28: note: called from here
   27 |     : reflect_mask_helper_0(
      |       ~~~~~~~~~~~~~~~~~~~~~^
   28 |         i - 1,
      |         ~~~~~~              
   29 |         reflect_mask_helper_1<Type>(
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   30 |           select(i - 1, 0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   31 |                         0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000),
      |                         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   32 |           1 << (i - 1),
      |           ~~~~~~~~~~~~~     
   33 |           value));
      |           ~~~~~~~
Run Code Online (Sandbox Code Playgroud)

Sto*_*ica 13

select实际上并不自称。它弹出收到的类型列表的前面,然后调用另一个专业化select<Type, ...>。尾随参数包不同。由于“递归”本质上是嵌套函数调用(不同函数)的有限集合,因此,无论运行时参数如何,GCC都能正确查看。

但是reflect_mask_helper_0确实会无限期地使用相同的模板参数调用自身。GCC无法告诉您此运行时递归在运行时将进行到多深。回想一下,一个constexpr函数仍然是一个常规函数,必须在运行时才能调用。

  • @AndrévonKugland-尝试在整个对象周围添加一个未命名的名称空间。模板和内部链接说明符有时不会完全胶合。如果编译器可以真正证明它在其他TU中未使用,则它可能能够抛弃无效代码。 (2认同)

And*_*and 4

这里\xe2\x80\x99s是我\xe2\x80\x99s找到的解决方案,感谢grek40\xe2\x80\x99s评论StoryTeller\xe2\x80\x99s答案

\n\n

(至于我之前在编译的二进制文件中留下未使用的函数模板实例的问题,我通过编译原始代码 \xe2\x80\x94 来解决它,而不使用参数\xe2\x80\x94gnu::always_inlinegnu::flatten属性 \xe2\x80\x94 -ffunction-sections -fdata-sections -Wl,--gc-sections。)

\n\n

现在reflect_mask_helper_0是在a里面struct(因为C++不允许函数模板的部分特化),i函数的参数就变成了模板Index的参数struct

\n\n
#include <iostream>\n#include <limits.h>\n\n// End recursive template-expansion of function select below.\ntemplate <typename Type>\nstatic inline constexpr Type select(unsigned index)\n{ return Type(); }\n\n// Select one of the items passed to it.\n// e.g. select(0, a, b, c) = a; select(1, a, b, c) = b; etc.\ntemplate <typename Type, typename... Params>\n[[gnu::always_inline]]\nstatic inline constexpr Type select(unsigned index, Type value, Params... values)\n{ return index == 0 ? value : select<Type>(index - 1, values...); }\n\ntemplate <typename Type>\n[[gnu::always_inline]]\nstatic inline constexpr Type reflect_mask_helper_1(Type mask, Type shift, Type value)\n{ return ((value & mask) >> shift) | ((value << shift) & mask); }\n\ntemplate <typename Type, unsigned Index>\nstruct reflect_mask_helper_0\n{\n  [[gnu::always_inline]]\n  static inline constexpr Type invoke(Type value)\n  {\n    return reflect_mask_helper_0<Type, Index - 1>::call(\n      reflect_mask_helper_1<Type>(\n        static_cast<Type>(select(Index - 1,\n          0xaaaaaaaaaaaaaaaa, 0xcccccccccccccccc, 0xf0f0f0f0f0f0f0f0,\n          0xff00ff00ff00ff00, 0xffff0000ffff0000, 0xffffffff00000000)),\n        1 << (Index - 1),\n        value));\n  }\n};\n\ntemplate <typename Type>\nstruct reflect_mask_helper_0<Type, 0>\n{\n  [[gnu::always_inline]]\n  static inline constexpr Type invoke(Type value) { return value; }\n};\n\ntemplate <typename Type>\nstatic inline constexpr Type reflect_mask(Type value)\n{ return reflect_mask_helper_0<Type, __builtin_ctz(sizeof(Type) * CHAR_BIT)>::invoke(value); }\n\nint main(void) {\n  for (int i = 0; i < 65536; i++) {\n    std::cout << reflect_mask<uint16_t>(i) << std::endl;\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n