constexpr超载
相关: 函数返回constexpr不编译
我觉得constexpr在C ++ 11中的用处是有限的,因为它不能定义两个函数,否则它们会有相同的签名,但是一个是constexpr,另一个是不是constexpr。 换句话说,如果我可以有一个constexst std :: string构造函数,它只需要constexpr参数,而一个非constexpr std :: string构造函数用于非constexpr参数将是非常有帮助的。 另一个例子是理论上复杂的函数,可以通过使用状态来提高效率。 你不能用constexpr函数轻松做到这一点,所以你只剩下两个select:如果你传递了非constexpr参数,或者完全放弃了constexpr(或者写了两个单独的函数,但你可能不知道要调用哪个版本)。
因此,我的问题是:
标准兼容的C ++ 11实现是否可以允许基于constexpr参数的函数重载,还是需要更新标准呢? 如果不允许,是否故意不允许?
@NicolBolas:说我有一个函数,将一个enum
映射到一个std::string
。 假设我的enum
从0
到n - 1
,最直接的方法是创build一个大小为n
的数组,填充结果。
我可以创build一个static constexpr char const * []
并在返回时构造一个std::string
(每次调用该函数时支付创build一个std::string
对象的代价),或者我可以创build一个static std::string const []
并返回我查找的值,在第一次调用函数时支付所有std::string
构造函数的代价。 看起来更好的解决scheme是在编译时在内存中创buildstd::string
(类似于char const *
所做的),但唯一的方法是提醒构造函数它有constexpr
参数。
除了std::string
构造函数以外,我认为find一个例子,如果你可以忽略constexpr
的需求(从而创build一个非constexpr
函数),那么你可以创build一个更多的例子高效的function。 考虑这个线程: constexpr问题,为什么这两个不同的程序在g ++中运行的时间不同?
如果我用一个constexpr
参数来调用fib
,那么我完全可以比编译器优化掉函数调用来做得更好。 但是如果我用一个非constexpr
参数来调用fib
,我可能希望让它调用我自己的版本来实现像memoization(这将需要状态),所以我得到的运行时间类似于我通过编译的时间一个constexpr
论据。
根据结果是否constexpr
,而不是参数,必须重载。
一个const std::string
可以存储一个指向该文字的指针,知道它永远不会被写入(使用const_cast
从std::string
删除const
将是必要的,这已经是未定义的行为)。 只需要存储布尔标志来禁止在销毁期间释放缓冲区。
但是,即使从constexpr
参数初始化,非const
string也需要dynamic分配,因为需要一个可写的副本,因此不应使用假设的constexpr
构造函数。
从标准(第7.1.6.1节[dcl.type.cv]
),修改任何被创build的对象const
是未定义的行为:
除了任何声明为mutable的类成员(7.1.1)可以修改之外,任何在其生命周期(3.8)中修改const对象的尝试都会导致未定义的行为。
我同意这个function缺失 – 我也需要它。 例:
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; }
现在我必须使用模板来做到这一点:
template <int n> static inline double pow (double x) { // fast implementation of x ^ n, with na compile time constant return ... }
这很好,但我错过了超载的机会。 如果我为其他人使用库函数,那么根据n是否为编译时间常数,用户必须使用不同的函数调用是不方便的,并且可能难以预测编译器是否将n减less为a编译时间常量与否。
如上所述,问题感觉不对 。
一个std::string
,通过构造,拥有内存。 如果你想要一个简单的引用到现有的缓冲区,你可以使用类似于llvm::StringRef
东西:
class StringRef { public: constexpr StringRef(char const* d, size_t s): data(d), size(s) {} private: char const* data; size_t size; };
当然,对于其他所有Cfunction来说,并没有什么不好的地方 。 这感觉就像标准的缺陷(想想所有的mathfunction…)。
至于国家,你可以(一点),只要你明白如何存储它。 请记住,循环相当于recursion? 那么,同样,你可以通过将它作为parameter passing给一个辅助函数来“存储”状态。
// potentially unsafe (non-limited) constexpr int length(char const* c) { return *c == '\0' ? 0 : 1 + length(c+1); } // OR a safer version constexpr int length_helper(char const* c, unsigned limit) { return *c == '\0' or limit <= 0 ? 0 : 1 + length_helper(c+1, limit-1); } constexpr int length256(char const* c) { return length_helper(c, 256); }
当然,这种状态的forms是有限的(你不能使用复杂的构造),这是constexpr
的限制。 但这已经是一个巨大的飞跃。 进一步意味着更深入到纯度(这在C ++中几乎是不可能的)。
虽然在C ++ 11中没有“constexpr overloading”这样的事情,但是仍然可以使用GCC / Clang __builtin_constant_p
内部函数。 请注意,这个优化对于double pow(double)
来说并不是很有用,因为GCC和Clang已经可以为常量积分指数优化pow,但是如果你编写了一个多精度或者vector库,那么这个优化就可以工作。
检查这个例子:
#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); }
在这个例子中, my_pow(a, x)
将被扩展为a*a*a*a
(感谢my_pow(a, b)
代码消除), my_pow(a, b)
将被扩展为直接调用generic_pow
而不需要任何初步检查。
检测和使用constexpr
不能像其他人那样重载已经答复,但这不是我们通常需要的。
我们有两个不同的algorithm,一个是constexpr
,另一个不是。 我们可以通过select“手动”来实现正确的select,然后用预处理macros缩短它。
通常这样的重载只是通过不同的algorithm达到相同的结果。 在这里为了testing的目的,我拿了两个algorithm,给出了从不相同的答案
#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; }
这是一个检测constexpr
的技巧。 我很确定它是由Johannes Schaub发明的,但我找不到这个引用。 非常好和清楚的把戏。
template<typename T> constexpr typename std::remove_reference<T>::type makeprval(T && t) { return t; } #define isprvalconstexpr(e) noexcept(makeprval(e))
所以剩下的就是写一个“foo”来select基于参数types的algorithm并对其进行testing:
#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; }
预期的产出是:
42 2 6 42
我在这里尝试的less数编译器像预期的那样工作。
标准兼容的C ++ 11实现是否可以允许基于constexpr参数的函数重载,还是需要更新标准呢? 如果不允许,是否故意不允许?
如果标准不说你可以做一些事情,那么允许某人做这件事就会是不规范的行为。 因此,一个允许它的编译器将会实现一个语言扩展。
毕竟,这不一定是坏事。 但它不符合C ++ 11。
我们只能猜测标准委员会的意图。 他们可能故意不允许,或者可能是一些疏忽。 事实是标准不允许超载,因此不是。