Dav*_*one 45 c++ overloading compile-time-constant constexpr c++11
我觉得constexpr在C++ 11中的用处有限,因为无法定义两个本来具有相同签名的函数,但有一个是constexpr而另一个不是constexpr.换句话说,如果我有一个constexpr std :: string构造函数只接受constexpr参数,并且非constexpr std :: string构造函数用于非constexpr参数,那将非常有用.另一个例子是理论上复杂的功能,通过使用状态可以提高效率.使用constexpr函数你不能轻易做到这一点,所以你有两个选择:如果你传入非constexpr参数,那么constexpr函数非常慢,或者完全放弃constexpr(或写两个单独的函数,但你可能不知道要调用哪个版本).
因此,我的问题是:
是否有可能符合标准的C++ 11实现允许基于constexpr参数的函数重载,或者这需要更新标准?如果不允许,是否故意不允许?
@NicolBolas:假设我有一个映射enum
到a 的函数std::string
.最直接的方式做到这一点,假设我enum
去从0
到n - 1
,是创建一个大小的数组n
充满了结果.
我可以创建一个static constexpr char const * []
并构造一个std::string
返回(std::string
每次调用函数时支付创建对象的成本),或者我可以创建一个static std::string const []
并返回我查找的值,std::string
第一次支付所有构造函数的成本调用该函数.似乎更好的解决方案是std::string
在编译时创建内存(类似于现在所做的char const *
),但是执行此操作的唯一方法是警告构造函数它有constexpr
参数.
对于一个除了std::string
构造函数之外的例子,我认为找到一个例子是非常简单的,如果你可以忽略constexpr
(并因此创建一个非constexpr
函数)的要求,你可以创建一个更有效的函数.考虑一下这个帖子:constexpr问题,为什么这两个不同的程序用g ++在不同的时间内运行?
如果我fib
用一个constexpr
参数调用,我不能比编译器完全优化掉函数调用做得更好.但是,如果我fib
使用非constexpr
参数调用,我可能希望让它调用我自己的版本来实现memoization(这将需要状态)之类的东西,所以我得到的运行时间类似于我通过constexpr
参数时的编译时间.
A F*_*Fog 35
我同意这个功能缺失 - 我也需要它.例:
double pow(double x, int n) {
// calculate x to the power of n
return ...
}
static inline double pow (double x, constexpr int n) {
// a faster implementation is possible when n is a compile time constant
return ...
}
double myfunction (double a, int b) {
double x, y;
x = pow(a, b); // call version 1 unless b becomes a compile time constant by inlining
y = pow(a, 5), // call version 2
return x + y;
}
Run Code Online (Sandbox Code Playgroud)
现在我必须使用模板执行此操作:
template <int n>
static inline double pow (double x) {
// fast implementation of x ^ n, with n a compile time constant
return ...
}
Run Code Online (Sandbox Code Playgroud)
这很好,但我错过了超载的机会.如果我为其他人创建一个库函数,那么根据n是否是编译时常量,用户必须使用不同的函数调用是不方便的,并且可能难以预测编译器是否已将n减少为a编译时间常数与否.
constexpr
无法使用重载进行检测(就像其他已经回复过的那样),但重载只是一种方法.
典型的问题是我们不能使用可以提高运行时性能的东西(例如调用非constexpr
函数或缓存结果)constexpr
.因此,我们最终可能会得到两种不同的算法,一种效率较低但可写constexpr
,其他算法优化为快速运行但不运行constexpr
.然后我们希望编译器不要constexpr
为运行时值选择算法,反之亦然.
这可以通过constexpr
"手动"基于它进行检测和选择,然后使用预处理器宏缩短界面来实现.
首先让我们有两个功能.通常,函数应该使用不同的算法达到相同的结果.我选择了两种算法,这些算法在这里从不给出相同的答案,只是为了测试和说明这个想
#include <iostream> // handy for test I/O
#include <type_traits> // handy for dealing with types
// run-time "foo" is always ultimate answer
int foo_runtime(int)
{
return 42;
}
// compile-time "foo" is factorial
constexpr int foo_compiletime(int num)
{
return num > 1 ? foo_compiletime(num - 1) * num : 1;
}
Run Code Online (Sandbox Code Playgroud)
然后我们需要一种方法来检测该参数是编译时常量表达式.如果我们不想像以前那样使用特定于编译器的方法, __builtin_constant_p
那么也有办法在标准C++中检测它.我很确定以下技巧是由Johannes Schaub发明的,但我找不到引用.非常好的和明确的技巧.
template<typename T>
constexpr typename std::remove_reference<T>::type makeprval(T && t)
{
return t;
}
#define isprvalconstexpr(e) noexcept(makeprval(e))
Run Code Online (Sandbox Code Playgroud)
该noexcept
操作者需要工作的编译时间等分支基础上会被大多数编译器优化了.所以现在我们可以编写一个"foo"宏,根据参数的constexprness选择算法并测试它:
#define foo(X) (isprvalconstexpr(X)?foo_compiletime(X):foo_runtime(X))
int main(int argc, char *argv[])
{
int a = 1;
const int b = 2;
constexpr int c = 3;
const int d = argc;
std::cout << foo(a) << std::endl;
std::cout << foo(b) << std::endl;
std::cout << foo(c) << std::endl;
std::cout << foo(d) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
预期产出是:
42
2
6
42
Run Code Online (Sandbox Code Playgroud)
在我尝试的少数编译器上,它的工作方式与预期的一样.
它必须根据结果是否重载constexpr
而不是参数.
一个const std::string
可以存储指向字面,知道它永远不会被写入(使用const_cast
删除const
从std::string
是必要的,而这已经未定义行为).只需要存储一个布尔标志来禁止在销毁期间释放缓冲区.
但是非const
字符串,即使从constexpr
参数初始化,也需要动态分配,因为需要参数的可写副本,因此constexpr
不应使用假设的构造函数.
从标准(第7.1.6.1节[dcl.type.cv]
),修改任何创建的对象const
是未定义的行为:
除了可以修改任何声明为可变的类成员(7.1.1)之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为.
虽然在C++ 11中没有"constexpr重载"这样的东西,但你仍然可以使用GCC/Clang __builtin_constant_p
内在函数.注意,这种优化并不是非常有用double pow(double)
,因为GCC和Clang已经可以为常量积分指数优化pow,但是如果你编写了一个多精度或矢量库,那么这种优化应该可行.
检查此示例:
#define my_pow(a, b) (__builtin_constant_p(b) ? optimized_pow(a, b) : generic_pow(a, b))
double generic_pow(double a, double b);
__attribute__((always_inline)) inline double optimized_pow(double a, double b) {
if (b == 0.0) return 1.0;
if (b == 1.0) return a;
if (b == 2.0) return a * a;
if (b == 3.0) return a * a * a;
if (b == 4.0) return a * a * a * a;
return generic_pow(a, b);
}
double test(double a, double b) {
double x = 2.0 + 2.0;
return my_pow(a, x) + my_pow(a, b);
}
Run Code Online (Sandbox Code Playgroud)
在这个例子my_pow(a, x)
中将扩展为a*a*a*a
(由于死代码消除),并将my_pow(a, b)
扩展到直接generic_pow
调用,无需任何初步检查.
std::is_constant_evaluated
中<type_traits>
。我在@\xc3\x96\xc3\xb6 Tiib在他的回答中提到的应用程序中遇到了这个问题,记住:我想在运行时使用更快的算法,但constexpr
在编译时使用更慢(且友好)的算法。
使用@\xc3\x96\xc3\xb6 Tiib的答案中的示例:
\n#include <iostream>\n#include <type_traits>\n\nconstexpr int foo(int i) {\n if (std::is_constant_evaluated()) {\n // compile-time branch\n return (i > 1) ? foo(i - 1) * i : 1;\n } else {\n // runtime branch\n return 42;\n }\n}\n\nint main(int argc, char* argv[]) {\n int a = foo(1);\n const int b = foo(2);\n constexpr int c = foo(3);\n const int d = foo(argc);\n\n std::cout << a << std::endl;\n std::cout << b << std::endl;\n std::cout << c << std::endl;\n std::cout << d << std::endl;\n}\n
Run Code Online (Sandbox Code Playgroud)\n给出输出
\n2\n2\n6\n42\n
Run Code Online (Sandbox Code Playgroud)\n\n请注意,这foo
确实是一个合法的constexpr
函数,因为正如标准所说,(引用自cppreference复制):
\n\n函数
\nconstexpr
必须满足以下要求:\n
\n- 至少存在一组参数值,使得函数的调用可以是核心常量表达式的计算子表达式(对于构造函数,在常量初始值设定项中使用就足够了)。
\n
但请注意,该程序:
\n#include <iostream>\n#include <type_traits>\n\nconstexpr int foo(int i) {\n if (std::is_constant_evaluated()) {\n // compile-time branch\n return i > 1 ? foo(i - 1) * i : 1;\n } else {\n // runtime branch\n return 42;\n }\n}\n\nint main(int argc, char *argv[]) {\n int a = 1;\n const int b = 2;\n constexpr int c = 3;\n const int d = argc;\n\n std::cout << foo(a) << std::endl;\n std::cout << foo(b) << std::endl;\n std::cout << foo(c) << std::endl;\n std::cout << foo(d) << std::endl;\n}\n
Run Code Online (Sandbox Code Playgroud)\n给出输出
\n42\n42\n42\n42\n
Run Code Online (Sandbox Code Playgroud)\n\n我不完全确定为什么会发生这种情况,但我怀疑这是因为std::cout
未operator<<
标记constexpr
,因此所有函数调用都foo
在运行时发生。然而有趣的是,在 Godbolt 的汇编输出(对于 x86-64 GCC 11.2)中,我们可以看到42
s 内联。因此该函数确实在编译时进行评估,只是不是按照我们最初期望的方式进行评估。
归档时间: |
|
查看次数: |
8601 次 |
最近记录: |