constexpr重载

Dav*_*one 45 c++ overloading compile-time-constant constexpr c++11

相关:函数返回constexpr无法编译

我觉得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去从0n - 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编译时间常数与否.

  • 这将是标准的一个很好的补充 (10认同)

Öö *_*iib 9

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)

在我尝试的少数编译器上,它的工作方式与预期的一样.


Ben*_*igt 7

它必须根据结果是否重载constexpr而不是参数.

一个const std::string可以存储指向字面,知道它永远不会被写入(使用const_cast删除conststd::string是必要的,而这已经未定义行为).只需要存储一个布尔标志来禁止在销毁期间释放缓冲区.

但是非const字符串,即使从constexpr参数初始化,也需要动态分配,因为需要参数的可写副本,因此constexpr不应使用假设的构造函数.


从标准(第7.1.6.1节[dcl.type.cv]),修改任何创建的对象const是未定义的行为:

除了可以修改任何声明为可变的类成员(7.1.1)之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为.


Loc*_*kal 6

虽然在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调用,无需任何初步检查.

  • 很遗憾`__builtin_constant_p`对constexpr函数参数不起作用. (2认同)

wer*_*mos 6

TL;DR:在 C++20 中是可能的,在标头std::is_constant_evaluated<type_traits>

\n

我在@\xc3\x96\xc3\xb6 Tiib他的回答中提到的应用程序中遇到了这个问题,记住:我想在运行时使用更快的算法,但constexpr在编译时使用更慢(且友好)的算法。

\n
\n

使用@\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

给出输出

\n
2\n2\n6\n42\n
Run Code Online (Sandbox Code Playgroud)\n

这是godbolt 上程序的链接

\n

请注意,这foo确实是一个合法的constexpr函数,因为正如标准所说,(引用自cppreference复制):

\n
\n

函数constexpr必须满足以下要求:

\n
    \n
  • 至少存在一组参数值,使得函数的调用可以是核心常量表达式的计算子表达式(对于构造函数,在常量初始值设定项中使用就足够了)。
  • \n
\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

给出输出

\n
42\n42\n42\n42\n
Run Code Online (Sandbox Code Playgroud)\n

(神箭链接)

\n

我不完全确定为什么会发生这种情况,但我怀疑这是因为std::coutoperator<<标记constexpr,因此所有函数调用都foo在运行时发生。然而有趣的是,在 Godbolt 的汇编输出(对于 x86-64 GCC 11.2)中,我们可以看到42s 内联。因此该函数确实在编译时进行评估,只是不是按照我们最初期望的方式进行评估。

\n