constexpr 真的需要吗?

发布于 2024-11-07 04:06:25 字数 1388 浏览 0 评论 0原文

我一直在研究 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];
}

Update

第二个示例实际上不是标准 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 函数。然而,我想不出任何实际应用程序中我曾经想要使用如此复杂的计算来定义编译时常量。

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

I have been looking at the new constexpr feature of C++ and I do not fully understand the need for it.

For example, the following code:

constexpr int MaxSize()
{
    ...

    return ...;
}

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

can be replaced by:

int MaxSize()
{
    ...

    return ...;
}

static const int s_maxSize = MaxSize();

foo()
{
    int vec[s_maxSize];
}

Update

The second example is actually not standard ISO C++ (thanks to several users for pointing this out) but certain compilers (e.g. gcc) support it. So it is not const that makes the program valid, but the fact that gcc supports this non-standard feature. (To my knowledge, this is possible only when the array is defined as local to a function or method, since the size of a global array must still be known at compile time.) If I compile without the options -std=c++98 -pedantic-errors, even the code

int MaxSize()
{
    return 10;
}

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

will compile with gcc.

So I will try to rephrase my question taking into account the feedback that came so far (and also some further reading I have done in the mean time).

I use the const keyword heavily. With const I can define a constant that has a certain value during its whole lifetime. A constant can be initialized with any expression, which is evaluated once, namely when the constant is created. For these cases, I think that constexpr is pretty useless: it would introduce a very small optimization in that the expression defining the constant value would be computed at compile time instead of run time. Every time I need a run-time constant with a complex initialization I use the keyword const.

So constexpr may come in handy in situations where we need to initialize a constant at compile time. One example is a vector definition: the standard does not support defining the size at runtime. Another example is a template with one or more non-type parameters.

In such cases I normally use macros:

#define MAX_SIZE (10)

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

but, if I understand correctly, constexpr functions are more powerful than macros, since they allow recursive calls of constexpr functions in their definition. However, I cannot think of any practical application in which I ever wanted to use such a complex computation to define a compile-time constant.

So, even if it may be an interesting feature, I still wonder if it is needed (i.e. how often it can solve situations where macros are not enough). Maybe looking at a few real-life examples that cannot be solved with macros would help me to change this opinion.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

浅浅淡淡 2024-11-14 04:06:25

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

这就是你的答案。实际上你不能按照你在 C++ 中建议的那样去做。它只能在 C++0x 中使用 constexpr 完成。

我还想指出,这段代码也适用于 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++ 中的编译器扩展来表达。

int vec[s_maxSize]; is actually illegal in the second example, so that is not possible to do in C++. But your first example is perfectly legal C++0x.

So there's your answer. You can't actually do what you propose in C++. It can only be done in C++0x with constexpr.

I would also like to point out, that this code also works in C++0x. Doing this in C++ would require some really fancy class templates.

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)];

Of course, in C++0x you still have to use the ternary operator instead of if statements. But, it works and is still a lot easier to understand than the template version you'd be force to use in 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];

And then, of course, in C++ you'd still have to write the gcd function if you needed to compute things at runtime because the template can't be used with arguments that vary at runtime. And C++0x would have the additional optimization boost of knowing that the result of the function is completely determined by the passed in arguments, which is a fact that can only be expressed with compiler extensions in C++.

凡尘雨 2024-11-14 04:06:25

您可以使用 constexpr 执行但无法使用宏或模板执行的操作,即在编译时解析/处理字符串: 使用 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");  
}

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

Something you can do with constexpr that you can not do with macros or templates is parsing /processing strings at compile time: Compile time string processing (changing case, sorting etc.) with constexpr. As a small excerpt from the preceding link, constexpr allows one to write code such as:

#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");  
}

As an example of when this could be useful, it could facilitate the compile time computation/construction of certain parsers and regular expression finite-state-machines that are specified with literal strings. And the more processing you can push off to compile time, the less processing you do at run time.

-小熊_ 2024-11-14 04:06:25
int MaxSize() {
    ...

    return ...; }

static const int s_maxSize = MaxSize();

int vec[s_maxSize];

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

int MaxSize() {
    ...

    return ...; }

static const int s_maxSize = MaxSize();

int vec[s_maxSize];

No, it can't. That's not legal C++03. You have a compiler extension that can allocate variable-length arrays.

我乃一代侩神 2024-11-14 04:06:25

constexpr 的另一个巧妙技巧允许 if 检测 编译时未定义的行为,这看起来是一个非常有用的工具。以下示例取自我链接的问题,使用 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 ;
}

这会导致 (现场观看):

true
false
true

Another neat trick that constexpr allows if to detect undefined behavior at compile time which looks like a very useful tool. The following example taken from the question I linked uses SFINAE to detect if an addition would cause overflow:

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

which results in (see it live):

true
false
true
趁年轻赶紧闹 2024-11-14 04:06:25

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 ?有没有什么害处啊!

constexpr allows the following to work:

#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 arguments really do need to be constant-expressions. The only reason your example works is because of Variable Length Arrays (VLAs) - a feature that is not in standard C++, but might be in many compilers as an extension.

A more interesting question might be: Why not put constexpr on every (const) function? Does it do any harm!?

提赋 2024-11-14 04:06:25

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

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

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

By that reasoning you don't need constants in general, not even #define. No inline functions or anything.

The point of constexpr, like so many keywords, is to let you express your intent better so the compiler understands exactly what you want instead of just what you're telling it, so it can do better optimizations for you behind the scenes.

In this example, it lets you write maintainable functions to calculate vector sizes instead of just plain text that you copy and paste over and over.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文