constexpr真的需要吗?

时间:2011-05-16 11:35:43

标签: c++ c++11 constexpr

我一直在研究C ++的新constexpr功能,但我并不完全理解它的必要性。

例如,以下代码:

constexpr int MaxSize()
{
    ...

    return ...;
}

void foo()
{
    int vec[MaxSize()];
}

可以替换为:

int MaxSize()
{
    ...

    return ...;
}

static const int s_maxSize = MaxSize();

foo()
{
    int vec[s_maxSize];
}

更新

第二个例子实际上不是标准的ISO C ++(感谢几个用户指出这一点),但某些编译器(例如gcc)支持它。因此,const不是使程序有效,而是gcc支持这种非标准功能的事实。 (据我所知,只有当数组被定义为函数或方法的本地时才有可能,因为在编译时仍必须知道全局数组的大小。)如果我编译时没有选项-std=c++98 -pedantic-errors,甚至代码

int MaxSize()
{
    return 10;
}

void foo()
{
    int vec[MaxSize()];
}

将使用gcc进行编译。

因此,考虑到目前为止的反馈(以及我在同一时间进行的一些进一步阅读),我将尝试重新解释我的问题。

我大量使用const关键字。使用const,我可以定义一个在整个生命周期内具有特定值的常量。可以使用任何表达式初始化常量,该表达式被计算一次,即创建常量时。对于这些情况,我认为constexpr是没有用的:它会引入一个非常小的优化,因为定义常量值的表达式将在编译时而不是运行时计算。每次我需要复杂初始化的运行时常量时,我​​都使用关键字const

因此,constexpr可能会在我们需要在编译时初始化常量的情况下派上用场。一个示例是矢量定义:标准不支持在运行时定义大小。另一个例子是带有一个或多个非类型参数的模板。

在这种情况下,我通常使用宏:

#define MAX_SIZE (10)

void foo()
{
    int vec[MAX_SIZE];
}

但是,如果我理解正确,constexpr函数比宏更强大,因为它们允许在其定义中递归调用constexpr函数。但是,我想不出任何实际的应用程序,我曾想用这种复杂的计算来定义编译时常量。

所以,即使它可能是一个有趣的功能,我仍然想知道是否需要它(即它可以解决宏不足的情况)。也许看一些用宏无法解决的现实例子可以帮助我改变这种观点。

6 个答案:

答案 0 :(得分:18)

int vec[s_maxSize];在第二个例子中实际上是非法的,因此在C ++中是不可能的。但是你的第一个例子是完全合法的C ++ 0x。

所以这是你的答案。你实际上无法做你在C ++中提出的建议。它只能在带有constexpr的C ++ 0x中完成。

我还想指出,这段代码也适用于C ++ 0x。在C ++中执行此操作需要一些非常精美的类模板。

constexpr unsigned int gcd(unsigned int const a, unsigned int const b)
{
   return (a < b) ? gcd(b, a) : ((a % b == 0) ? b : gcd(b, a % b));
}

char vec[gcd(30, 162)];

当然,在C ++ 0x中,您仍然必须使用三元运算符而不是if语句。但是,它比你在C ++中强制使用的模板版本更有效,并且更容易理解:

template <unsigned int a, unsigned int b>
class gcdT {
 public:
   static unsigned int const value = gcdT<b, a % b>::value;
};

template <unsigned int a>
class gcdT<a, 0> {
 public:
   static unsigned int const value = a;

};

char vec[gcdT<30, 162>::value];

然后,当然,在C ++中,如果需要在运行时计算内容,则仍需要编写gcd函数,因为模板不能与运行时变化的参数一起使用。知道函数的结果完全由传入的参数决定,C ++ 0x会有额外的优化提升,这是一个事实,只能用C ++中的编译器扩展来表达。

答案 1 :(得分:7)

你可以用constexpr做的事情是你无法用宏或模板做的事情是在编译时解析/处理字符串:Compile time string processing (changing case, sorting etc.) with constexpr。作为上述链接的一小段摘录,constexpr允许用户编写如下代码:

#include "my_constexpr_string.h"
int main()
{
   using namespace hel;
   #define SDUMP(...) static_assert(__VA_ARGS__, "")

   SDUMP(tail("abc") == "bc");
   SDUMP( append("abc", "efgh") == "abcefgh" );
   SDUMP( prepend("abc", "efgh") == "efghabc" );
   SDUMP( extract<1,3>("help") == "el" );
   SDUMP( insert<1>("jim", "abc") == "jabcim" );
   SDUMP( remove("zabzbcdzaz", 'z') == "abbcdazzzz" );
   SDUMP( erase("z12z34z5z", 'z') == "12345"  );
   SDUMP( map("abc", ToUpper()) == "ABC" );
   SDUMP( find("0123456777a", '7') == 7 );
   SDUMP( isort("03217645") == "01234567");  
}

作为一个有用的例子,它可以促进编译时计算/构造某些解析器和使用文字字符串指定的正则表达式有限状态机。而且你可以推动编译时间越多,你在运行时的处理就越少。

答案 2 :(得分:3)

int MaxSize() {
    ...

    return ...; }

static const int s_maxSize = MaxSize();

int vec[s_maxSize];

不,它不能。这不是合法的C ++ 03。您有一个可以分配可变长度数组的编译器扩展。

答案 3 :(得分:0)

constexpr允许以下工作:

#include<iostream>
using namespace std;

constexpr int n_constexpr() { return 3; }
int n_NOTconstexpr() { return 3; }


template<size_t n>
struct Array { typedef int type[n]; };

typedef Array<n_constexpr()>::type vec_t1;
typedef Array<n_NOTconstexpr()>::type vec_t2; // fails because it's not a constant-expression

static const int s_maxSize = n_NOTconstexpr();
typedef Array<s_maxSize>::type vec_t3; // fails because it's not a constant-expression

template参数确实需要是常量表达式。您的示例工作的唯一原因是因为可变长度数组(VLA) - 一种不在标准C ++中的功能,但可能在许多编译器中作为扩展。

一个更有趣的问题可能是:为什么不在每个(const)函数上放置constexpr?它有害吗??

答案 4 :(得分:0)

另一个巧妙的技巧 constexpr 允许在编译时检测undefined behavior,这看起来是一个非常有用的工具。从我链接的问题中获取的以下示例使用 SFINAE 来检测添加是否会导致溢出:

#include <iostream>
#include <limits>

template <typename T1, typename T2>
struct addIsDefined
{
     template <T1 t1, T2 t2>
     static constexpr bool isDefined()
     {
         return isDefinedHelper<t1,t2>(0) ;
     }

     template <T1 t1, T2 t2, decltype( t1 + t2 ) result = t1+t2>
     static constexpr bool isDefinedHelper(int)
     {
         return true ;
     }

     template <T1 t1, T2 t2>
     static constexpr bool isDefinedHelper(...)
     {
         return false ;
     }
};


int main()
{    
    std::cout << std::boolalpha <<
      addIsDefined<int,int>::isDefined<10,10>() << std::endl ;
    std::cout << std::boolalpha <<
     addIsDefined<int,int>::isDefined<std::numeric_limits<int>::max(),1>() << std::endl ;
    std::cout << std::boolalpha <<
      addIsDefined<unsigned int,unsigned int>::isDefined<std::numeric_limits<unsigned int>::max(),std::numeric_limits<unsigned int>::max()>() << std::endl ;
}

导致( see it live ):

true
false
true

答案 5 :(得分:-4)

根据这个推理,你通常不需要常量,甚至不需要#define。没有内联函数或任何东西。

与许多关键字一样,constexpr的目的是让您更好地表达您的意图,以便编译器准确理解您想要的而不仅仅是你告诉它什么,所以它可以在幕后为你做更好的优化。

在此示例中,它允许您编写可维护的函数来计算矢量大小,而不仅仅是您反复复制和粘贴的纯文本。

相关问题