【问题标题】:Can I detect at compile time "function arguments" that are compile-time constants我可以在编译时检测作为编译时常量的“函数参数”吗
【发布时间】:2017-12-06 00:54:03
【问题描述】:

能否在编译时检测“函数参数”1 是否为编译时常量?

例如一个函数print(int i),如果调用为print(5),则可以打印"constant 5",但如果调用为print(i),则"non-constant 5",其中i是一些非常量变量。特别是,在“恒定”分支中,我应该能够将 i 视为 constexpr,包括将其用于模板参数等。

宏技巧、模板元编程和 SFINAE 技巧都可以。理想情况下,它是可移植的,但特定于编译器的解决方案总比没有好。

如果存在“假阴性”也没关系 - 即,如果常量值有时被检测为非常量(例如,当某些优化被禁用时)。

如果解决方案可以检测到常量值何时间接传递给函数(例如,当常量值传递给调用print 并且随后内联将常量暴露给print 的中间函数时),则加分.最后一种行为显然取决于优化。

如果自然扩展到多个参数,则加倍加分。

如果可以有带和不带constexpr 参数的函数的重载版本,这可能很简单,但是you can't


1 我在这里将“函数参数”放在引号中,因为该解决方案并不严格要求在函数内(或在调用者/被调用者边界使用特殊参数检测此状态) - 它只需像函数一样出现在调用者面前,但可以使用宏或其他技巧,例如带有 operator() 等的静态对象。

【问题讨论】:

  • 我记得 GCC 有类似 __builtin_constant_p 的东西。但是,使用宏,我确信有更多可移植的方式,但该宏肯定必须面向用户。
  • 可以在此处检测到 constexpr:stackoverflow.com/a/15236647/1294207 也许可以提供帮助?
  • 顺便说一句,您说“简单地”使用 constexpr 参数重载,但实际上并非如此。在提出标准化建议时,这引发了巨大的讨论。
  • @max66 - 对,但它不必是一个普通函数 void print(int i) - 它可以是一个类似函数的宏,它可以在其参数上发挥一些魔力,并根据具体情况调用不同的函数无论是常数,还是某种模板魔法。我只是说代码的用户应该编写类似print(5) 的东西,并且在内部我希望能够采用constexpr 代码路径,具体取决于参数是否可以用作constexpr。
  • 您可以使用参数计数宏技巧的变体将宏解决方案扩展到多个参数(请参阅:stackoverflow.com/q/11317474/315052),因此您可以定义Print1Print2,...等, 然后Print(...) 将根据参数计数宏的结果调用正确的PrintN 宏。

标签: c++ c++11 optimization constexpr


【解决方案1】:

它不必是一个普通的函数 void print(int i) - 它可以是一个类似函数的宏,它在它的参数上做一些魔术,并根据它是一个常量还是调用一个不同的函数可能是一些模板魔术

你说的“类函数宏”?

嗯...首先我要警告你,C 风格的类函数宏是危险的。蒸馏邪恶,恕我直言。

这么说,如果你真的接受基于宏的解决方案,我想将它与constexpr 方法、模板structstatic 局部变量和SFINAE 结合起来......

如果定义如下模板PrintStructstruct

template <typename T>
struct PrintStruct
 {
   template <bool>
   static void func (...) 
    { std::cout << "func non-const: " << T::func(true) << std::endl; }

   template <bool b, int I = T::func(b)>
   static void func (int) 
    { std::cout << "func const:     " << I << std::endl; }
 };

和下面的 C 风格函数类宏,它定义了一个 foo 本地 struct 并将其作为模板参数传递给 PrintStruct 以激活 SFINAE 以选择所需的 func()(并调用 @ 987654331@,显然)[EDIT:由 jxh 改进的宏以使其扩展为语句;谢谢!] [EDIT 2:在观察 OP 后修改了宏以接受表达式]

#define Print(i)                          \
[&]()                                     \
 {                                        \
   static int const printLocalVar { i };  \
                                          \
   struct local_foo                       \
    {                                     \
      static constexpr int func (bool b)  \
       { return b ? printLocalVar : 0; }  \
    } ;                                   \
                                          \
   PrintStruct<local_foo>::func<true>(0); \
 }                                        \
()

观察打印的值,在PrintStruct::func()的const版本中,是一个模板整数值; so 也可用于模板参数、C 样式数组维度、static_assert()s 测试等。

不确定这是完全标准的(我不是真正的专家)并且可以做你真正想要的,但以下是一个完整的工作示例

#include <iostream>

template <typename T>
struct PrintStruct
 {
   template <bool>
   static void func (...) 
    { std::cout << "func non-const: " << T::func(true) << std::endl; }

   template <bool b, int I = T::func(b)>
   static void func (int) 
    { std::cout << "func const:     " << I << std::endl; }
 };


#define Print(i)                          \
[&]()                                     \
 {                                        \
   static int const printLocalVar { i };  \
                                          \
   struct local_foo                       \
    {                                     \
      static constexpr int func (bool b)  \
       { return b ? printLocalVar : 0; }  \
    } ;                                   \
                                          \
   PrintStruct<local_foo>::func<true>(0); \
 }                                        \
()

int main()
 {
   constexpr int  i { 2 };
   int const      j { 3 };
   int            k { 4 };
   int const      l { k+1 };

   Print(1);    // print func const:     1
   Print(i);    // print func const:     2
   Print(j);    // print func const:     3
   Print(k);    // print func non-const: 4
   Print(l);    // print func non-const: 5
   Print(2+2);  // print func const:     4
 }

【讨论】:

  • dowhile(0)包围宏的语句块,这样宏就会展开成语句。这避免了else 的语法问题,例如if (cond) Print(x); else { some; other; stuff; }
  • @max66:这就是在 C 中为宏所做的。对于 C++,我想您可以尝试将您的块转换为直接调用的 lambda,这将为您提供一个表达式。
  • @jxh - 我用 C 语言工作了很多年(很多年前),但我不记得这个技巧了;关于 lambda ......好吧,我没有办法做到这一点;但也许我错了......今天我没有时间,但也许明天,我会尝试。如果您成功地将我的宏转换为 lambda,请将其写为答案。我对其他类型的问题感兴趣。
  • @BeeOnRope - 正确:不适用于表达式;关于val ## i 是为了避免你调用传递一个变量到Print 与本地静态变量的名称相同;但是考虑到这会阻止与表达式一起使用,我认为这是一个坏主意,最好为局部变量修复一个又长又奇怪的名称。
  • @BeeOnRope - 修改宏以接受表达式(但不要使用包含 printLocalVar 的表达式调用它:( )
【解决方案2】:

为了检测constexpr 的适用性,可以考虑这个GCC-only suggestion from @JohannesSchaub-litb(有关限制,请参阅链接答案):

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

不同案例的工作示例读取

#include <iostream>

////////////////////////////////////////////////////////////////////////////////

// https://stackoverflow.com/a/13305072/2615118

template<class T>
constexpr std::remove_reference_t<T> makeprval(T&& t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

////////////////////////////////////////////////////////////////////////////////

template<bool is_constexpr, class Lambda>
struct HybridArg {
  using T = std::invoke_result_t<Lambda>;

  Lambda lambda_;
  constexpr operator T() const { return lambda_(); }// implicit conversion
};

template<bool is_constexpr, class Lambda>
constexpr auto make_hybrid_arg(Lambda lambda) {
  return HybridArg<is_constexpr, Lambda>{lambda};
}

#define WRAP_ARG(arg)                     \
  make_hybrid_arg<isprvalconstexpr(arg)>( \
    [&] { return arg; }                   \
  )                                       \

////////////////////////////////////////////////////////////////////////////////

template<int i>
void print_impl_constexpr() {
  std::cout << i << ": ";
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

void print_impl_fallback(int i) {
  std::cout << i << ": ";
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

////////////////////////////////////////////////////////////////////////////////

// option 1 (for choosing implementation):
// compile-time introspection

template<class Arg>
struct is_constexpr_arg : std::false_type {};

template<class Lambda>
struct is_constexpr_arg<
  HybridArg<true, Lambda>
> : std::true_type {};

template<class Arg>
void print_by_introspection(Arg arg) {
  if constexpr(is_constexpr_arg<Arg>{}) {
    print_impl_constexpr<arg>();
  }
  else {
    print_impl_fallback(arg);
  }
}

////////////////////////////////////////////////////////////////////////////////

// option 2 (for choosing implementation):
// overload

void print_by_overload(int arg) {
  print_impl_fallback(arg);
}

template<class Lambda>
void print_by_overload(HybridArg<true, Lambda> arg) {
  print_impl_constexpr<arg>();
}

////////////////////////////////////////////////////////////////////////////////

template<class Arg>
void indirection(Arg arg) {
  print_by_introspection(arg);
  print_by_overload(arg);
}

void bad_indirection(int arg) {
  print_by_introspection(arg);
  print_by_overload(arg);
}

////////////////////////////////////////////////////////////////////////////////

int main() {
  {
    int i = 0;
    indirection(i);
  }
  {
    int i = 1;
    indirection(WRAP_ARG(i));
  }
  {
    constexpr int i = 2;
    indirection(WRAP_ARG(i));
  }
  {
    constexpr int i = 3;
    bad_indirection(WRAP_ARG(i));
  }
}

用 GCC 编译后的输出:

0: void print_impl_fallback(int)
0: void print_impl_fallback(int)
1: void print_impl_fallback(int)
1: void print_impl_fallback(int)
2: void print_impl_constexpr() [with int i = 2]
2: void print_impl_constexpr() [with int i = 2]
3: void print_impl_fallback(int)
3: void print_impl_fallback(int)

【讨论】:

  • 除非我遗漏了什么,否则这需要调用者明确标记每个参数是否为常量。如果您愿意施加这种限制,似乎有许多可能的解决方案 - 例如,只需调用 print&lt;5&gt;() 而不是 print(5) 用于常量参数(至少对于模板类型参数中允许的类型)。这个想法(我认为困难的部分)是自动检测参数的常量性,而不需要调用者的太多合作。
  • @BeeOnRope:是的,在我编辑之前,调用者有责任标记参数类型。如果您对自动标记的 C++17 尝试感兴趣,请参阅我的更新答案。
  • 不过,用 Clang 完成这项工作似乎是不可能的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 2013-06-16
  • 2013-09-29
  • 1970-01-01
  • 2012-02-13
相关资源
最近更新 更多