【问题标题】:When should you use constexpr capability in C++11?什么时候应该在 C++11 中使用 constexpr 功能?
【发布时间】:2011-06-12 11:53:51
【问题描述】:

在我看来,拥有一个“总是返回 5 的函数”会破坏或淡化“调用函数”的含义。必须有一个原因,或者需要这种能力,否则它不会出现在 C++11 中。为什么会在那里?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

在我看来,如果我编写了一个返回文字值的函数,并且我进行了代码审查,那么有人会告诉我,我应该声明一个常量值而不是写 return 5。

【问题讨论】:

  • 你能定义一个返回 constexpr 的递归函数吗?如果是这样,我可以看到一个用法。
  • 我相信这个问题应该说明“如果编译器可以自己推断出一个函数是否可以在编译时评估,为什么要引入一个新的关键字(!)”。让它“由关键字保证”听起来不错,但我想我希望尽可能保证它,而不需要关键字。
  • @Kos :更熟悉 C++ 内部的人可能更喜欢你的问题,但我的问题来自一个以前编写过 C 代码但不熟悉 C++ 2011 关键字的人的角度完全没有,也没有 C++ 编译器的实现细节。能够推理编译器优化和常量表达式推导是一个比这个更高级的用户问题的主题。
  • @Kos 我的想法和你一样,我想出的答案是,如果没有 constexpr,你怎么会(很容易)知道编译器实际上编译时为您评估了该功能?我想您可以检查程序集输出以查看它做了什么,但更容易告诉编译器您需要该优化,如果由于某种原因它不能为您做到这一点,它会给您一个很好的编译 -错误而不是默默地未能优化您期望它优化的地方。
  • @Kos:你可以对const 说同样的话。事实上,强制意图有用的!数组维度是典型的例子。

标签: c++ c++11 constexpr


【解决方案1】:

假设它做了一些更复杂的事情。

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

现在,您可以将某些东西评估为常数,同时保持良好的可读性,并允许比仅将常数设置为数字更复杂的处理。

它基本上为可维护性提供了很好的帮助,因为您正在做的事情变得更加明显。以max( a, b ) 为例:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

这是一个非常简单的选择,但这确实意味着如果您使用常量值调用 max,它会在编译时而不是在运行时显式计算。

另一个很好的例子是DegreesToRadians 函数。每个人都发现度数比弧度更容易阅读。虽然您可能知道 180 度是 3.14159265 (Pi) 的弧度,但它更清楚地写成如下:

const float oneeighty = DegreesToRadians( 180.0f );

这里有很多很好的信息:

http://en.cppreference.com/w/cpp/language/constexpr

【讨论】:

  • 它告诉编译器在编译时尝试计算值的优点。我很好奇为什么 const 在指定特定优化时不提供此功能?还是这样?
  • @Tamus:通常它会但它没有义务这样做。 constexpr 强制编译器,如果它不能,就会吐出一个错误。
  • 我现在看到了。 Sin(0.5) 是另一个。这巧妙地替换了 C 宏。
  • 我可以将其视为一个新的面试问题:解释 const 和 constexpr 关键字之间的区别。
  • 作为我自己记录这一点的一种方式,我编写了与上面类似的代码,并且函数是“const”而不是“constexpr”。当我使用 Clang3.3、-pedantic-errors 和 -std=c++11 时,我预计后者不会编译。它像在“constexpr”案例中一样编译和运行。你认为这是一个 clang 扩展,还是在这篇文章得到回复后对 C++11 规范进行了调整?
【解决方案2】:

何时使用constexpr

  1. 只要有编译时间常数。

【讨论】:

  • 虽然我同意你的观点,但这个答案并没有解释 为什么 constexpr 应该优先于预处理器宏或 const
【解决方案3】:

所有其他答案都很棒,我只想举一个很酷的例子来说明你可以用 constexpr 做的一件事,这太棒了。 See-Phit (https://github.com/rep-movsd/see-phit/blob/master/seephit.h) 是一个编译时 HTML 解析器和模板引擎。这意味着您可以将 HTML 放入并取出可以操作的树。在编译时完成解析可以为您带来一些额外的性能。

来自 github 页面示例:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}

【讨论】:

    【解决方案4】:

    它对类似的东西很有用

    // constants:
    const int MeaningOfLife = 42;
    
    // constexpr-function:
    constexpr int MeaningOfLife () { return 42; }
    
    int some_arr[MeaningOfLife()];
    

    将它与特征类或类似的东西结合起来,它变得非常有用。

    【讨论】:

    • 在您的示例中,它比普通常量提供零优势,因此它并不能真正回答问题。
    • 这是一个人为的例子,想象一下,如果 MeaningOfLife() 从其他地方获得它的价值,比如另一个函数或 #define 或系列。你可能不知道它返回了什么,它可能是库代码。其他示例,想象一个具有 constexpr size() 方法的不可变容器。你现在可以做 int arr[container.size()];
    • @plivesey 你能用一个更好的例子来编辑你的答案吗?
    【解决方案5】:

    简介

    constexpr 没有被引入来告诉实现可以在需要 constant-expression 的上下文中评估某些东西;在 C++11 之前,符合标准的实现已经能够证明这一点。

    实现无法证明的是某段代码的意图

    • 开发者想用这个实体表达什么?
    • 我们是否应该盲目地允许代码在 constant-expression 中使用,只是因为它恰好可以工作?

    如果没有constexpr,世界将会怎样?

    假设您正在开发一个库并意识到您希望能够计算区间 (0,N] 中每个整数的总和。

    int f (int n) {
      return n > 0 ? n + f (n-1) : n;
    }
    

    缺乏意图

    如果传递的参数在翻译过程中是已知的,编译器可以很容易地证明上述函数可以在 constant-expression 中调用;但您还没有将其声明为意图 - 恰好是这种情况。

    现在其他人出现了,读取您的函数,进行与编译器相同的分析; “哦,这个函数可以在常量表达式中使用!”,然后编写以下代码。

    T arr[f(10)]; // freakin' magic
    

    优化

    您,作为一个"awesome" 库开发者,决定f 在被调用时应该缓存结果;谁会想一遍又一遍地计算同一组值?

    int func (int n) { 
      static std::map<int, int> _cached;
    
      if (_cached.find (n) == _cached.end ()) 
        _cached[n] = n > 0 ? n + func (n-1) : n;
    
      return _cached[n];
    }
    

    结果

    通过引入你愚蠢的优化,你打破了你的函数的每一次使用,而这些使用恰好是在需要 constant-expression 的上下文中。

    您从未承诺过该函数可以在 constant-expression 中使用,如果没有constexpr,就无法提供这样的承诺。


    那么,为什么我们需要constexpr

    constexpr的主要用途是声明intent

    如果一个实体没有被标记为constexpr - 它从未打算用于常量表达式;即使是这样,我们也依赖编译器来诊断这种上下文(因为它无视我们的意图)。

    【讨论】:

    • 这可能是正确的答案,因为最近 C++14 和 C++17 的变化允许在 constexpr 表达式中使用更广泛的语言。换句话说,几乎所有 anything 都可以注释 constexpr(也许有一天它会因为这个而消失?),除非有一个何时使用 constexpr 或不,几乎所有的代码都会这样写。
    • @alecov 绝对不是所有...I/Osyscalldynamic memory allocation 绝对不能标记为constexpr 此外,并非所有都应该 constexpr.
    • @alecov 有些函数是在运行时执行的,在编译时执行是没有意义的。
    • 我也最喜欢这个答案。编译时评估是一种巧妙的优化,但你真正从constexpr 得到的是某种行为的保证。就像const 一样。
    • 什么编译器允许int f (int n) { return n &gt; 0 ? n + f (n-1) : n;} T arr[f(10)]; 的这个无 constexpr 版本我无法在任何地方编译?
    【解决方案6】:

    刚刚开始将项目切换到 c++11,并且遇到了一个非常好的 constexpr 情况,它清理了执行相同操作的替代方法。这里的关键点是,你只能在声明为 constexpr 时将函数放入数组大小声明中。在许多情况下,我可以看到这对我所涉及的代码领域的发展非常有用。

    constexpr size_t GetMaxIPV4StringLength()
    {
        return ( sizeof( "255.255.255.255" ) );
    }
    
    void SomeIPFunction()
    {
        char szIPAddress[ GetMaxIPV4StringLength() ];
        SomeIPGetFunction( szIPAddress );
    }
    

    【讨论】:

    • 这同样可以写成: const size_t MaxIPV4StringLength = sizeof("255.255.255.255");
    • static inline constexpr const auto 可能更好。
    【解决方案7】:

    constexpr 函数非常好,是对 c++ 的一个很好的补充。但是,您是对的,它解决的大多数问题都可以用宏来解决。

    但是,constexpr 的用途之一没有 C++03 等效的类型化常量。

    // This is bad for obvious reasons.
    #define ONE 1;
    
    // This works most of the time but isn't fully typed.
    enum { TWO = 2 };
    
    // This doesn't compile
    enum { pi = 3.1415f };
    
    // This is a file local lvalue masquerading as a global
    // rvalue.  It works most of the time.  But May subtly break
    // with static initialization order issues, eg pi = 0 for some files.
    static const float pi = 3.1415f;
    
    // This is a true constant rvalue
    constexpr float pi = 3.1415f;
    
    // Haven't you always wanted to do this?
    // constexpr std::string awesome = "oh yeah!!!";
    // UPDATE: sadly std::string lacks a constexpr ctor
    
    struct A
    {
       static const int four = 4;
       static const int five = 5;
       constexpr int six = 6;
    };
    
    int main()
    {
       &A::four; // linker error
       &A::six; // compiler error
    
       // EXTREMELY subtle linker error
       int i = rand()? A::four: A::five;
       // It not safe use static const class variables with the ternary operator!
    }
    
    //Adding this to any cpp file would fix the linker error.
    //int A::four;
    //int A::six;
    

    【讨论】:

    • 您能否澄清一下“极其微妙的链接器错误”?或者至少提供一个说明的指针?
    • @enobayram,三元运算符获取操作数的地址。这从代码中并不明显。一切编译正常,但链接失败,因为four 的地址无法解析。我必须深入挖掘才能确定谁在获取我的 static const 变量的地址。
    • “这很糟糕,原因很明显”:最明显的原因是分号,对吧?
    • “极其微妙的链接器错误”让我完全困惑。 fourfive 都不在范围内。
    • 另见新的enum class 类型,它修复了一些枚举问题。
    【解决方案8】:

    来自 Stroustrup 在“Going Native 2012”上的演讲:

    template<int M, int K, int S> struct Unit { // a unit in the MKS system
           enum { m=M, kg=K, s=S };
    };
    
    template<typename Unit> // a magnitude with a unit 
    struct Value {
           double val;   // the magnitude 
           explicit Value(double d) : val(d) {} // construct a Value from a double 
    };
    
    using Speed = Value<Unit<1,0,-1>>;  // meters/second type
    using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
    using Second = Unit<0,0,1>;  // unit: sec
    using Second2 = Unit<0,0,2>; // unit: second*second 
    
    constexpr Value<Second> operator"" s(long double d)
       // a f-p literal suffixed by ‘s’
    {
      return Value<Second> (d);  
    }   
    
    constexpr Value<Second2> operator"" s2(long double d)
      // a f-p literal  suffixed by ‘s2’ 
    {
      return Value<Second2> (d); 
    }
    
    Speed sp1 = 100m/9.8s; // very fast for a human 
    Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
    Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
    Acceleration acc = sp1/0.5s; // too fast for a human
    

    【讨论】:

    • 这个例子也可以在Stroustrup的论文Software Development for Infrastructure中找到。
    • clang-3.3:错误:constexpr 函数的返回类型 'Value' 不是文字类型
    • 这很好,但谁把文字放在这样的代码中。如果你正在编写一个交互式计算器,让你的编译器为你“检查你的单位”是有意义的。
    • @bobobobo 或者如果你正在为火星气候轨道飞行器编写导航软件,也许 :)
    • 使其编译 - 1. 在文字后缀中使用下划线。 2.为100_m添加运算符“”_m。 3. 使用 100.0_m,或者添加一个接受 unsigned long long 的重载。 4. 声明 Value 构造函数 constexpr。 5. 将对应的运算符 / 添加到 Value 类中,如下所示: constexpr auto operator / (const Value& other) const { return Value::TheUnit::m, TheUnit ::kg - 值::TheUnit::kg, TheUnit::s - 值::TheUnit::s>>(val / other.val); }。其中 TheUnit 是在 Value 类中添加的 Unit 的 typedef。
    【解决方案9】:

    曾经有一种元编程模式:

    template<unsigned T>
    struct Fact {
        enum Enum {
            VALUE = Fact<T-1>*T;
        };
    };
    
    template<>
    struct Fact<1u> {
        enum Enum {
            VALUE = 1;
        };
    };
    
    // Fact<10>::VALUE is known be a compile-time constant
    

    我相信引入 constexpr 是为了让您编写这样的构造,而无需模板和具有专业化、SFINAE 和其他东西的怪异构造 - 但就像您编写运行时函数一样,但保证结果将在编译时确定。

    但是,请注意:

    int fact(unsigned n) {
        if (n==1) return 1;
        return fact(n-1)*n;
    }
    
    int main() {
        return fact(10);
    }
    

    g++ -O3 编译它,你会看到fact(10) 确实在编译时被评估了!

    支持 VLA 的编译器(因此 C99 模式下的 C 编译器或具有 C99 扩展的 C++ 编译器)甚至可以让您执行以下操作:

    int main() {
        int tab[fact(10)];
        int tab2[std::max(20,30)];
    }
    

    但目前它是非标准 C++ - constexpr 看起来像是一种解决这个问题的方法(即使没有 VLA,在上述情况下)。而且仍然存在需要将“正式”常量表达式作为模板参数的问题。

    【讨论】:

    • 事实函数不会在编译时进行评估。它必须是 constexpr 并且必须只有一个 return 语句。
    • @Sumant:你是对的,它不必在编译时进行评估,但它是!我指的是编译器中真正发生的事情。在最近的 GCC 上编译它,查看生成的 asm,如果您不相信我,请自行检查!
    • 尝试添加std::array&lt;int, fact(2)&gt;,你会看到 fact() 在编译时没有被计算。只是 GCC 优化器做得很好。
    • 这就是我所说的......我真的那么不清楚吗?见最后一段
    【解决方案10】:

    另一个用途(尚未提及)是constexpr 构造函数。这允许创建不必在运行时初始化的编译时常量。

    const std::complex<double> meaning_of_imagination(0, 42); 
    

    将其与用户定义的文字配对,您就可以完全支持文字用户定义的类。

    3.14D + 42_i;
    

    【讨论】:

      【解决方案11】:

      它可以启用一些新的优化。 const 传统上是类型系统的提示,不能用于优化(例如,const 成员函数可以 const_cast 并且无论如何都可以合法地修改对象,因此不能信任 const 进行优化)。

      constexpr 表示表达式 really 是常量,前提是函数的输入是常量。考虑:

      class MyInterface {
      public:
          int GetNumber() const = 0;
      };
      

      如果这在其他模块中公开,编译器不能相信 GetNumber() 每次调用时都不会返回不同的值 - 即使是连续的,中间没有非常量调用 - 因为 const 可能有在执行中被抛弃。 (显然任何这样做的程序员都应该被枪决,但语言允许,因此编译器必须遵守规则。)

      添加constexpr

      class MyInterface {
      public:
          constexpr int GetNumber() const = 0;
      };
      

      编译器现在可以应用优化,其中GetNumber() 的返回值被缓存并消除对GetNumber() 的额外调用,因为constexpr 是返回值不会改变的更强有力的保证。

      【讨论】:

      • 实际上const 可以用于优化...即使在const_cast IIRC 之后修改值 defined const 也是未定义的行为.我希望它对于const 成员函数是一致的,但我需要用标准检查它。这意味着编译器可以在那里安全地进行优化。
      • @Warren:优化是否实际完成并不重要,它只是允许的。 @Kos:这是一个鲜为人知的微妙之处,如果 original 对象被 not 声明为 const(int xconst int x),那么修改它是安全的通过const_cast -ing const 指向它的指针/引用。否则,const_cast 将始终调用未定义的行为,并且毫无用处:) 在这种情况下,编译器没有关于原始对象的 const-ness 的信息,因此无法判断。
      • @Kos 我不认为 const_cast 是这里唯一的问题。 const 方法可以读取甚至修改全局变量。相反,来自另一个线程的人也可以在调用之间修改 const 对象。
      • “= 0”在此处无效,应删除。我会自己做,但我不确定这是否符合 SO 协议。
      • 两个例子都是无效的:第一个 (int GetNumber() const = 0;) 应该声明 GetNumber() 方法是虚拟的。第二个(constexpr int GetNumber() const = 0;)无效,因为纯说明符(= 0)暗示方法是虚拟的,但 constexpr 不能是虚拟的(参考:en.cppreference.com/w/cpp/language/constexpr
      【解决方案12】:

      您的基本示例与常量本身的论点相同。为什么使用

      static const int x = 5;
      int arr[x];
      

      结束

      int arr[5];
      

      因为它更易于维护。与现有的元编程技术相比,使用 constexpr 的读写速度要快得多。

      【讨论】:

        【解决方案13】:

        std::numeric_limits&lt;T&gt;::max():不管出于什么原因,这是一种方法。 constexpr 在这里会很有用。

        另一个例子:你想声明一个与另一个数组一样大的 C 数组(或 std::array)。目前这样做的方法是这样的:

        int x[10];
        int y[sizeof x / sizeof x[0]];
        

        但是会写不是更好吗:

        int y[size_of(x)];
        

        感谢constexpr,您可以:

        template <typename T, size_t N>
        constexpr size_t size_of(T (&)[N]) {
            return N;
        }
        

        【讨论】:

        • @Kos:不。它会返回一个运行时值。 constexpr 强制编译器使函数返回一个编译时值(如果可以的话)。
        • @Kos:没有constexpr,它不能用于数组大小声明,也不能用作模板参数,无论函数调用的结果是否是编译时常量。这两个基本上是constexpr 的唯一用例,但至少模板参数用例有点重要。
        • "不管出于什么原因,这是一个方法":原因是C++03中只有编译时整数,而没有其他编译时类型,所以只有一个方法可以用于任意C++11 之前的类型。
        • @LwCui 不,这不是“好的”:默认情况下,GCC 在某些事情上只是松懈。使用-pedantic 选项,它将被标记为错误。
        • @SexyBeast 不确定你的意思? int 大小在编译时已知,常量 10 在编译时已知,因此数组大小在编译时也已知,运行时没有“调用”
        【解决方案14】:

        根据我的阅读,对 constexpr 的需求来自元编程中的一个问题。特征类可能具有表示为函数的常量,例如:numeric_limits::max()。使用 constexpr,这些类型的函数可以用于元编程,或者用作数组边界等。

        我想到的另一个例子是,对于类接口,您可能希望派生类型为某些操作定义自己的常量。

        编辑:

        在探索了 SO 之后,看起来其他人已经提出了 some examples 的 constexprs 可能实现的功能。

        【讨论】:

        • “要成为界面的一部分,你必须成为一个函数”?
        • 现在我看到了它的用处,我对 C++ 0x 有点兴奋。这似乎是一件经过深思熟虑的事情。我知道他们一定是。那些语言标准的超级极客很少做随机的事情。
        • 我对 lambdas、线程模型、initializer_list、rvalue 引用、可变参数模板、新的绑定重载...非常期待。
        • 哦,是的,但我已经了解其他几种语言的 lambdas/closures。 constexpr 在具有强大编译时表达式评估系统的编译器中特别有用。 C++ 在该领域确实没有同行。 (这是对 C++11 的强烈赞扬,恕我直言)
        猜你喜欢
        • 2010-09-17
        • 2010-12-30
        • 1970-01-01
        • 2010-10-18
        • 2011-06-16
        • 1970-01-01
        相关资源
        最近更新 更多