什么时候应该在C++ 11中使用constexpr功能?

War*_* P 322 c++ constexpr c++11

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

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
Run Code Online (Sandbox Code Playgroud)

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

Goz*_*Goz 289

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

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

const int meaningOfLife = MeaningOfLife( 6, 7 );
Run Code Online (Sandbox Code Playgroud)

现在,您可以将某些内容评估为常量,同时保持良好的可读性,并允许稍微复杂的处理,而不仅仅是将常量设置为数字.

它基本上为可维护性提供了很好的帮助,因为它正在变得更加明显.就拿max( a, b )例如:

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }
Run Code Online (Sandbox Code Playgroud)

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

另一个好例子是DegreesToRadians功能.每个人都发现学位比弧度更容易阅读.虽然您可能知道180度是弧度,但更清楚地写如下:

const float oneeighty = DegreesToRadians( 180.0f );
Run Code Online (Sandbox Code Playgroud)

这里有很多好消息:

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

  • 我现在看到了.罪(0.5)是另一个.这样可以整齐地替换C宏. (19认同)
  • 很好的一点,它告诉编译器尝试在编译时计算值.我很好奇为什么const在指定特定优化时不提供此功能?或者是吗? (17认同)
  • @Tamus:通常它会,但没有义务.constexpr要求编译器,如果不能,则会发出错误. (10认同)
  • 我可以将此视为一个新的面试问题:解释const和constexpr关键字之间的差异. (10认同)
  • 作为一种记录这一点的方法,我自己编写了类似的代码,并且函数是"const"而不是"constexpr".当我使用Clang3.3时,-pedantic-errors和-std = c ++ 11我希望后者不会编译.它编译并运行在"constexpr"案例中.你认为这是一个clang扩展还是已经对C++ 11规范进行了调整,因为这篇文章得到了解答? (2认同)
  • 后续:以下仅在函数为"constexpr" - > char myArray [meaningOfLife]时有效; (2认同)
  • @Goz我不认为 constexpr 要求编译器在编译时执行该函数 - 我非常确定如果您向 constexpr 函数提供一个非 const 值,它将在运行时运行它并且不会产生编译器错误。 (2认同)

Fil*_*efp 139

介绍

constexpr没有引入作为告诉实现的方法,可以在需要常量表达的上下文中评估某些内容; 符合实现已经能够在C++ 11之前证明这一点.

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

  • 开发人员想用这个实体表达什么?
  • 我们应该盲目地允许代码在常量表达式中使用,只是因为它恰好起作用了吗?

没有世界会是constexpr什么?

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

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}
Run Code Online (Sandbox Code Playgroud)

缺乏意图

如果传递的参数在转换期间是已知的,编译器可以很容易地证明上述函数在常量表达式中是可调用的.但你没有宣称这是一个意图 - 事实恰恰相反.

现在别人出现,读取你的函数,做与编译器相同的分析; " 哦,这个函数可用于常量表达!" ,并编写以下代码.

T arr[f(10)]; // freakin' magic
Run Code Online (Sandbox Code Playgroud)

优化

作为一个"令人敬畏"的库开发人员,您决定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];
}
Run Code Online (Sandbox Code Playgroud)

结果

通过引入您的愚蠢优化,您只是打破了函数的每个用法,这些用法恰好位于需要常量表达式的上下文中.

你从来没有承诺该函数的是使用常数表达式,没有constexpr就没有提供这样的承诺的方式.


那么,我们为什么需要constexpr呢?

constexpr的主要用途是声明意图.

如果实体未标记为constexpr- 它从未打算用于常量表达式 ; 即使它是,我们依靠编译器来诊断这样的上下文(因为它忽略了我们的意图).

  • 这可能是正确的答案,因为最近C++ 14和C++ 17的变化允许在`constexpr`表达式中使用更广泛的语言.换句话说,几乎_anything_可以注释`constexpr`(可能有一天它会因为这个而消失?),除非有一个标准何时使用`constexpr`,几乎所有的代码将被编写. (23认同)
  • @alecov并不是所有的东西都绝对存在...```I / O`,``''syscall```和```动态内存分配`''绝对不能标记为``constexpr`'' ,并不是所有的东西都应该是“ constexpr”。 (4认同)
  • 我也最喜欢这个答案。编译时评估是一种巧妙的优化,但你真正从 `constexpr` 得到的是某种行为的保证。就像 `const` 一样。 (2认同)

Kon*_*lph 91

std::numeric_limits<T>::max():无论出于何种原因,这是一种方法.constexpr这将是有益的.

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

int x[10];
int y[sizeof x / sizeof x[0]];
Run Code Online (Sandbox Code Playgroud)

但是能写的不是更好:

int y[size_of(x)];
Run Code Online (Sandbox Code Playgroud)

谢谢你constexpr,你可以:

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}
Run Code Online (Sandbox Code Playgroud)

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

def*_*ode 19

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;
Run Code Online (Sandbox Code Playgroud)

  • "由于显而易见的原因,这很糟糕":最明显的原因是分号,对吗? (23认同)
  • 你能澄清一下"非常微妙的链接器错误"吗?或者至少提供一个澄清指针? (12认同)
  • @enobayram,三元运算符获取操作数的地址.这在代码中并不明显.一切都编译好,但链接失败,因为"四"的地址无法解决.我必须真正挖掘出谁正在获取我的`static const`变量的地址. (4认同)
  • "极其微妙的链接器错误"让我完全不解."四"和"五"都不在范围内. (4认同)
  • 另见新的`enum class`类型,它修复了一些枚举问题. (3认同)
  • @MooingDuck:包含大小的字符串对象很好.此外,使用字符串文字也称为char数组需要在每次传递到带有std :: string的函数时复制整个事物.我认为std :: string兼容的constexpr对象具有明显的优势. (2认同)

luk*_*uke 14

从我所读到的,constexpr的需求来自元编程中的一个问题.Trait类可能有常量表示为函数,think:numeric_limits :: max().使用constexpr,这些类型的函数可用于元编程,或作为数组边界等.

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

编辑:

在探索SO之后,看起来其他人已经提出了一些 可能与constexprs有关的例子.


小智 11

来自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
Run Code Online (Sandbox Code Playgroud)

  • @bobobobo或者如果你正在为火星气候轨道器编写导航软件,也许:) (5认同)
  • 也可以在Stroustrup的论文《基础设施的软件开发》(http://www.stroustrup.com/Software-for-infrastructure.pdf)中找到该示例。 (2认同)

Mot*_*tti 7

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

const std::complex<double> meaning_of_imagination(0, 42); 
Run Code Online (Sandbox Code Playgroud)

与用户定义的文字配对,您完全支持文字用户定义的类.

3.14D + 42_i;
Run Code Online (Sandbox Code Playgroud)


Kos*_*Kos 6

曾经有一个元编程模式:

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
Run Code Online (Sandbox Code Playgroud)

我相信constexpr是为了让你编写这样的结构而不需要模板和带有特化,SFINAE和东西的奇怪结构 - 但是就像你编写一个运行时函数一样,但保证结果将在编译中确定-时间.

但请注意:

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

int main() {
    return fact(10);
}
Run Code Online (Sandbox Code Playgroud)

编译它,g++ -O3您将看到fact(10)在编译时确实已经得到了证实!

一个支持VLA的编译器(所以C99模式下的C编译器或带有C99扩展的C++编译器)甚至可以允许你这样做:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}
Run Code Online (Sandbox Code Playgroud)

但是目前它是非标准的C++ - constexpr看起来像是一种解决这个问题的方法(即使没有VLA,在上面的例子中).并且仍然存在需要将"正式"常量表达式作为模板参数的问题.

  • 这就是我说的……我真的有那么不清楚吗?见最后一段 (2认同)

jgi*_*bbs 5

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

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
Run Code Online (Sandbox Code Playgroud)

  • 这同样可以写成:const size_t MaxIPV4StringLength = sizeof("255.255.255.255"); (4认同)