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 。 假设我的enum0n - 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_caststd::string删除const将是必要的,这已经是未定义的行为)。 只需要存储布尔标志来禁止在销毁期间释放缓冲区。

但是,即使从constexpr参数初始化,非conststring也需要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。

我们只能猜测标准委员会的意图。 他们可能故意不允许,或者可能是一些疏忽。 事实是标准不允许超载,因此不是。