Let me start by stating my intent. In the olden (C++) days, we would have code like:
class C
{
public:
enum {SOME_VALUE=27};
};
Then we could use SOME_VALUE
throughout our code as a compile time constant and wherever the compiler would see C::SOME_VALUE
, it would just insert the literal 27.
Now days, it is seems more acceptable to change that code to something like:
class C
{
public:
static constexpr int SOME_VALUE=27;
};
This looks much cleaner, gives SOME_VALUE
a well defined type and seems to be the preferred approach as of C++11. The (unforseen at least for me) problem is that this also causes scenarios where SOME_VALUE
needs to be made external. That is, in some cpp file somewhere, we need to add:
constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage
The cases that cause this seem to be when const references to SOME_VALUE
are used, which happens quite often in C++ Standard Library code (See the example at the bottom of this question). I am using gcc 4.7.2 as my compiler by the way.
Due to this dilemma, I am forced to revert back to defining SOME_VALUE
as an enum (i.e., old school) in order to avoid having to add a definition to a cpp file for some, but not all of my static constexpr member variables. Isn't there some way to tell the compiler that constexpr int SOME_VALUE=27
means that SOME_VALUE
should be treated only as a compile time constant and never an object with external linkage? If you see a const reference used with it, create a temporary. If you see its address taken, generate a compile time error if that's what's needed, because it's a compile time constant and nothing more.
Here is some seemingly benign sample code that causes us to need to add the definition for SOME_VALUE
in a cpp file (once again, tested with 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
}
Adding the following line to the code at file scope will resolve the error:
constexpr int C::SOME_VALUE;
For the record, the static constexpr
version will work like you'd expected in C++17. From N4618 Annex D.1 [depr.static_constexpr]:
D.1 Redeclaration of static constexpr
data members [depr.static_constexpr]
For compatibility with prior C++ International Standards, a constexpr
static data member may be redundantly redeclared outside the class with no initializer. This usage is deprecated. [Example:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
—end example]
The relevant standard text that allows this is N4618 9.2.3 [class.static.data]/3:
[...] An inline static data member may be defined in the class definition and may specify a brace-or-equal-initializer. If the member is declared with the constexpr
specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see D.1). [...]
This comes with the same machinery that introduced the non-constexpr
version of the same thing, inline static data members.
struct A {
static inline int n = 5; // definition (illegal in C++ 2014)
};
inline int A::n; // illegal
You have three options here:
If your class is template, then put the definition of static member in header itself. Compiler is required to identify it as one definition only across multiple translation units (see [basic.def.odr]/5)
If your class is non-template you can easily put it in source file
Alternatively declare constexpr static member function getSomeValue():
class C
{
public:
static constexpr int getSomeValue() { return 27; }
};
I'd go with enum class:
http://en.cppreference.com/w/cpp/language/enum
http://www.stroustrup.com/C++11FAQ.html#enum
From the first link:
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
Nowadays, the preferred way is:
enum class : int C { SOME_VALUE = 5 };
From the C++ standard N3797 S3.5/2-3
A name is said to have linkage when it might denote the same object, reference, function, type, template, namespace or value as a name introduced by a declaration in another scope:
— When a name has external linkage , the entity it denotes can be referred to by names from scopes of other translation units or from other scopes of the same translation unit.
— When a name has internal linkage , the entity it denotes can be referred to by names from other scopes in the same translation unit.
— When a name has no linkage , the entity it denotes cannot be referred to by names from other scopes.
A name having namespace scope (3.3.6) has internal linkage if it is the name of
— a variable, function or function template that is explicitly declared static; or,
— a non-volatile variable that is explicitly declared const or constexpr and neither explicitly declared extern nor previously declared to have external linkage; or
— a data member of an anonymous union.
My reading is that in the following code:
public:
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
All 4 instances of SOME_VALUE
have internal linkage. They should link with a reference to SOME_VALUE
in the same translation unit and not be visible elsewhere.
Obviously the first one is a declaration and not a definition. It needs a definition within the same translation unit. If GCC says so and MSVC does not, then MSVC is wrong.
For the purposes of replacing an enum, number 2 should work fine. It still has internal linkage without the static
keyword.
[Edited in response to comment]
you can do this
class C
{
public:
static const int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE);
}
This is not even C++11, just C++98