枚举vs constexpr用于类内部的实际静态常量


69

让我首先说明我的意图。在较早的(C ++)时代,我们将有如下代码:

class C
{
public:
  enum {SOME_VALUE=27};
};

然后,我们可以SOME_VALUE在整个代码中将其用作编译时间常数,并且无论编译器看到什么C::SOME_VALUE,它都只会插入文字27。

现在,将代码更改为以下内容似乎是可以接受的:

class C
{
public:
  static constexpr int SOME_VALUE=27;
};

SOME_VALUE从C ++ 11开始,这看起来更加简洁,给出了定义良好的类型,似乎是首选方法。(至少对我来说是SOME_VALUE无法预料的)问题是,这还会导致需要将其置于外部的情况。也就是说,在某些cpp文件中,我们需要添加:

constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage

导致这种情况的原因似乎是在使用const引用时SOME_VALUE,这种情况在C ++标准库代码中经常发生(请参阅此问题底部的示例)。顺便说一下,我正在使用gcc 4.7.2作为我的编译器。

由于这一难题,我不得不回到定义SOME_VALUE为枚举(即旧派)的方式,以避免为某些但不是全部静态constexpr成员变量向cpp文件中添加定义。难道没有什么办法告诉编译器这constexpr int SOME_VALUE=27意味着SOME_VALUE应该将其视为编译时常数,而不应将其视为具有外部链接的对象吗?如果看到与它一起使用的const引用,请创建一个临时引用。如果您看到它的地址,那么就需要生成一个编译时错误,因为这是一个编译时间常数,仅此而已。

以下是一些看似良性的示例代码,这些代码使我们需要SOME_VALUE在cpp文件中添加的定义(再次,用gcc 4.7.2测试):

#include <vector>

class C
{
public:
  static constexpr int SOME_VALUE=5;
};

int main()
{
  std::vector<int> iv;

  iv.push_back(C::SOME_VALUE); // Will cause an undefined reference error
                               // at link time, because the compiler isn't smart
                               // enough to treat C::SOME_VALUE as the literal 5
                               // even though it's obvious at compile time
}

在文件范围的代码中添加以下行将解决该错误:

constexpr int C::SOME_VALUE;

1
我真的很困惑“何时使用SOME_VALUE……的地址,我被迫退回到定义SOME_VALUEenum”。枚举数是prvalue,您也不能使用它们的地址。
Ben Voigt 2014年

2
您可以使用static constexpr int SOME_VALUE() { return 5; }...
Jarod42 2014年

12
顺便说一句,您现在可以为枚举数提供一种定义明确的类型:enum : int { SOME_VALUE = 5 };
Ben Voigt 2014年

5
通过类似的问题和答案,例如[1 ],[2 ],[3 ],我在这里收集了许多解决方法。最简洁的是+SOME_VALUE用来获得临时工。
iavr

8
所有这些变通办法都向我表明,enum对于整数常数使用a比对a更好static constexpr。惊喜更少(无论如何至少有一个惊喜)。使用旧版C ++和C的好处也是。
Michael Burr

Answers:


10

作为记录,该static constexpr版本将像您在C ++ 17中所期望的那样工作。从N4618附件D.1 [depr.static_constexpr]

D.1static constexpr数据成员的重新声明[depr.static_constexpr]

为了与以前的C ++国际标准兼容,constexpr可以在没有初始化程序的类外部冗余地声明静态数据成员。不建议使用此用法。[示例:

struct A {
 static constexpr int n = 5; // definition (declaration in C++ 2014)
};

constexpr int A::n; // redundant declaration (definition in C++ 2014)

结束示例]

允许这样做的相关标准文本为N4618 9.2.3 [class.static.data] / 3

[...]内联静态数据成员可以在类定义中定义,并且可以指定大括号或相等初始化器。如果用说明constexpr符声明了该成员,则可以在没有初始化程序的命名空间范围内对其进行重新声明(不建议使用此用法;请参见D.1)。[...]

这与引入constexpr相同事物的非版本的相同机器(内联静态数据成员)一起使用

struct A {
 static inline int n = 5; // definition (illegal in C++ 2014)
}; 

inline int A::n; // illegal

1
我本人将对此进行更新,但是由于您已经完成了工作,所以我决定只接受您的回答,以帮助其他人调查此问题。
Michael Goldshteyn

9

您在这里有三个选择:

  1. 如果您的类是模板,则将静态成员的定义放在标头本身中。要求编译器仅跨多个翻译单元将其标识为一个定义(请参阅[basic.def.odr] / 5)

  2. 如果您的课程不是模板,则可以轻松地将其放在源文件中

  3. 或者声明constexpr静态成员函数getSomeValue():

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    

1

根据C ++标准N3797 S3.5 / 2-3

当一个名称可能表示与另一个作​​用域中的声明引入的名称相同的对象,引用,函数,类型,模板,名称空间或值时,该名称具有链接关系:

—当一个名称具有外部链接时,其表示的实体可以通过其他翻译单位范围或同一翻译单位其他范围内的名称进行引用。

—当一个名称具有内部链接时,它表示的实体可以由同一翻译单位中其他作用域的名称引用。

—当名称没有链接时,其表示的实体不能被其他作用域中的名称引用。

具有名称空间范围(3.3.6)的名称如果具有以下名称,则具有内部链接:

—明确声明为静态的变量,函数或函数模板;要么,

—一个非易失性变量,已明确声明为const或constexpr,既未明确声明为extern,也未事先声明为具有外部链接;要么

—匿名联合的数据成员。

我的阅读是下面的代码:

public:
  static constexpr int SOME_VALUE=5;
  constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;

的所有4个实例SOME_VALUE都有内部链接。它们应与SOME_VALUE同一个翻译单元中的参考链接在一起,并且在其他位置不可见。

显然,第一个是声明而不是定义。它需要在同一翻译单元中定义。如果GCC这么说,而MSVC没有,则MSVC是错误的。

为了替换枚举,数字2应该可以正常工作。它仍然具有内部链接,但没有static关键字。

[根据评论编辑]


@dyp:谢谢!一定是一个深夜。参见编辑。
david.pfx

您显示的代码不足以确定链接是否在外部。您需要像在我的示例中那样通过引用某些函数来传递该值,以使链接变得外部。
Michael Goldshteyn 2014年

@dyp:哎呀!第二句话还可以。固定。
david.pfx 2014年

我认为链接实际上不是问题。链接是名称的属性。就像我说的,将一些SOME_VALUE没有定义的静态数据成员绑定到引用会违反ODR,这不是链接问题。例如,您可以很好地使用没有链接(例如自动变量)或内部链接(例如静态全局变量)的名称作为的参数push_back
dyp 2014年

“这不是链接问题” <-可能会造成混淆,因为链接器无法在OP中找到变量的定义。我的意思是链接,即标准中定义的名称的可见性。
dyp

1

我会去枚举类:

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

http://www.stroustrup.com/C++11FAQ.html#enum

从第一个链接:

enum class Color { RED, GREEN=20, BLUE};
Color r = Color::BLUE;
switch(r) {
    case Color::RED : std::cout << "red\n"; break;
    case Color::GREEN : std::cout << "green\n"; break;
    case Color::BLUE : std::cout << "blue\n"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21

2
这给出了enumOP传递时提到的类型,但没有解决它们的主要问题,即使用static const[expr]'代替'时是否可以避免外部链接enum
underscore_d 2015年

OP明确需要编译时整数常量,而不是唯一类型。
Parker Coates

0

如今,首选方式是:

enum class : int C { SOME_VALUE = 5 };

3
但是现在,您需要int值时必须显式强制转换。
Chnossos 2014年

1
这给出了enumOP传递时提到的类型,但没有解决它们的主要问题,即使用static const[expr]'代替'时是否可以避免外部链接enum
underscore_d

为什么要在枚举上强加名称和类?我能理解的基础类型,但为什么要其余呢?
einpoklum

-1

你可以这样做

class C
{
public:
  static const int SOME_VALUE=5;
};

int main()
{
  std::vector<int> iv;
  iv.push_back(C::SOME_VALUE); 
}

这甚至不是C ++ 11,而是C ++ 98


1
当然可以,并且它不会编译的,其原因与OP的代码完全相同。
Frax 2014年

它像往常一样编译和运行VS 2012(我使用了多年)。请参阅此处stackoverflow.com/questions/2605520/… 以及此处publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/…以及此处devx.com/tips/Tip/5602
Ophir Gvirtzer 2014年

3
在VS2012中可以正常工作的事实并不能使其与所有其他编译器(例如gcc)兼容。总体而言,Microsoft在其编译器认为是错误的方面一直比较松懈(即,对开发人员的宽容态度而不是严格的标准合规性)。另外,如果在std :: vector中提供了push_back的按值版本,作为整数和/或小型POD类型的专门化,则可以消除链接器错误。这样的实现是否会违反该标准可能尚待解释。
Michael Goldshteyn 2014年

1
迈克尔,反之亦然,在这个问题上,VS遵循标准,而gcc则不遵循。我在标准9.4.2 / 4中发现了这一点-如果静态数据成员为const积分或const枚举类型,则其在类定义中的声明可以指定一个常量初始化器,该初始化器应为整数常量表达式(5.19)。在这种情况下,成员可以出现在整数常量表达式中。如果在程序中使用了该成员,则该成员仍应在命名空间范围内定义,并且该命名空间范围定义不应包含初始化程序。
Ophir Gvirtzer 2014年

1
@Ophir,不管是外部还是外部,这有什么关系?
Michael Goldshteyn 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.