53

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

我觉得 constexpr 在 C++11 中的用处有限,因为无法定义两个函数,否则它们将具有相同的签名,但一个是 constexpr,另一个不是 constexpr。换句话说,如果我可以拥有一个仅接受 constexpr 参数的 constexpr std::string 构造函数,以及一个用于非 constexpr 参数的非 constexpr std::string 构造函数,那将非常有帮助。另一个例子是理论上复杂的函数,可以通过使用状态来提高效率。你不能用 constexpr 函数轻易做到这一点,所以你有两个选择:如果你传入非 constexpr 参数,则有一个非常慢的 constexpr 函数,或者完全放弃 constexpr (或编写两个单独的函数,但您可能不知道要调用哪个版本)。

因此,我的问题是:

符合标准的 C++11 实现是否有可能允许基于 constexpr 的参数进行函数重载,或者这是否需要更新标准?如果不允许,是不是故意不允许的?


@NicolBolas:假设我有一个将 an 映射enum到 a的函数std::string。假设我enum0to 开始,最直接的方法是创建一个大小为结果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++ 在如此不同的时间内运行?

如果我fibconstexpr参数调用,我不能比编译器完全优化函数调用做得更好。但是,如果我fib使用非constexpr参数调用,我可能希望它调用我自己的版本,该版本实现了诸如 memoization 之类的东西(这将需要状态),所以我得到的运行时间类似于我传递constexpr参数时的编译时间.

4

8 回答 8

39

我同意缺少此功能-我也需要它。例子:

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 n a compile time constant
    return ...
}

这很好,但我错过了超载的机会。如果我制作了一个库函数供其他人使用,那么用户必须根据 n 是否为编译时间常数来使用不同的函数调用是很不方便的,并且可能很难预测编译器是否已将 n 减少到编译时间常数与否。

于 2012-04-01T08:23:18.723 回答
10

编辑:下面描述的技巧不再保证有效!

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;
}

然后我们需要一种方法来检测该参数是编译时常量表达式。如果我们不想使用特定于编译器的 __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))

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;
}

预期输出为:

42
2
6
42

在我尝试过的几个编译器上,它的工作方式与预期的一样。

于 2016-01-31T04:48:37.997 回答
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);
}

在此示例my_pow(a, x)中将扩展为a*a*a*a(感谢死代码消除),并将my_pow(a, b)扩展为直接generic_pow调用而无需任何初步检查。

于 2014-02-27T08:27:46.417 回答
8

它必须根据结果是否存在constexpr而不是参数来重载。

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

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


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

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

于 2012-01-20T04:12:37.103 回答
4

如前所述,这个问题感觉不对


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;
};

当然,还有strlen其他所有的 C 函数都没有 constexpr感觉像是标准的缺陷(想想所有的数学函数......)。


至于状态,你可以(有点),只要你了解如何存储它。还记得循环等同于递归吗?好吧,同样,您可以通过将状态作为参数传递给辅助函数来“存储”状态。

// 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); }

当然,这种状态的这种形式是有一定限制的(不能使用复杂的构造),这是constexpr. 但这已经是一个巨大的飞跃。更进一步意味着更深入地了解纯度(这在 C++ 中几乎是不可能的)。

于 2012-01-20T08:37:03.287 回答
2

符合标准的 C++11 实现是否有可能允许基于 constexpr 的参数进行函数重载,或者这是否需要更新标准?如果不允许,是不是故意不允许的?

如果标准没有说您可以做某事,那么允许某人这样做将是非标准行为。因此,允许它的编译器将实现语言扩展。

毕竟,这不一定是坏事。但它不符合 C++11。

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

于 2012-01-20T04:27:20.583 回答
1

可以使用Richard Smith 提出的基于缩小转换规则的方法来识别给定的静态存储变量是否为常量表达式。

我们可以在不缩小的情况下分配给unsigned intaconsexpr 非负数int

unsigned int u {std::max(0, -3)}; // compiles, max is constexpr

但是,如果我们使用变量,则无法执行上述操作:

int a = 3;
unsigned int u {std::max(0, a)}; // compilation error, narrowing int to unsigned int

为了确定一个给定的是否int reference是 const 表达式,我们可以测试它是否可以分配给一个而不用其正值负值unsigned int 缩小。对于在编译时已知的任何值,这应该是可能的,即可以被视为常量表达式。int

template<const int& p> std::true_type
    is_constexpr_impl(decltype((unsigned int){std::max(-p, p)}));
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr =
    decltype(is_constexpr_impl<p>(0));

现在我们可以使用宏方法对运行时和编译时间进行不同的实现:

int foo_runtime(int num) {
    return num;
}

constexpr int foo_compiletime(int num) {
      return num + 1;
}

#define foo(X) (is_constexpr<X>()?foo_compiletime(X):foo_runtime(X))

如前所述,它将模拟 const 表达式的重载

int main() {
    static int a = 3;
    static const int b = 42; // considered constexpr
    static const int c = foo_runtime(42); // not constexpr
    static constexpr int d = 4;
    static constexpr int e = -2;
    static int f = 0;
    static const int g = 0; // considered constexpr

    std::cout << foo(a) << std::endl;
    std::cout << foo(b) << std::endl;
    std::cout << foo(c) << std::endl;
    std::cout << foo(d) << std::endl;
    std::cout << foo(e) << std::endl;
    std::cout << foo(f) << std::endl;
    std::cout << foo(g) << std::endl;
}

上面很好,虽然不是很有用,因为它仅限于静态存储变量。但它确实存在基于constexpr.


另一种实现相同的方法,而不依赖于缩小转换,可以是

template<const int& p> std::true_type
    is_constexpr_impl(std::array<int, std::max(p, -p)>);
template<const int& p> std::false_type
    is_constexpr_impl(...);
template<const int& p> using is_constexpr = 
    decltype(is_constexpr_impl<p>(0));

上面的使用std::array替换了使用简单的 c 数组,这对于使用这种方法的 gcc 来说效果不佳


或者另一个 - 再次,不依赖于缩小规则 -也可以正常工作

template<const int& p, typename T = void>
struct is_constexpr: std::false_type {};

template<const int& p>
struct is_constexpr<p, std::void_t<int[std::max(p,-p)+1]>>: std::true_type {};

请注意,如果我们尝试使用更简单的方法来达到同样的效果:

template<typename T>
struct is_constexpr: std::false_type {};

template<typename T>
struct is_constexpr<const T>: std::true_type {};

#define foo(X) (is_constexpr<decltype(X)>()?foo_compiletime(X):foo_runtime(X))

我们不会实现这条线的目标:

static const int c = foo_runtime(42); // const but not constexpr
于 2020-03-17T00:57:11.210 回答
1

使用 SFINAE 检测编译时编译的另一个选项:http: //coliru.stacked-crooked.com/a/f3a2c11bcccdb5bf

template<typename T>
auto f(const T&)
{
  return 1;
}

constexpr auto f(int)
{
  return 2;
}



////////////////////////////////////////////////////////////////////////
template<typename T, int=f(T{})>
constexpr bool is_f_constexpr_for(int) {return true;}

template<typename...>
constexpr bool is_f_constexpr_for(...) {return false;}



template<typename T>
auto g(const T& t)
{
  if constexpr (is_f_constexpr_for<T>(0))
  {

  }
  else
  {

  }
}
于 2018-08-23T12:45:49.523 回答