78

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;
jww
  • 97,681
  • 90
  • 411
  • 885
Michael Goldshteyn
  • 71,784
  • 24
  • 131
  • 181
  • 1
    I'm really confused by "when the address of `SOME_VALUE` is taken, ... I am forced to revert back to defining `SOME_VALUE` as an `enum`". Enumerators are *prvalues*, you can't take their addresses either. – Ben Voigt Apr 04 '14 at 16:13
  • I've amended the question to only imply const references to the value, so that taking an address is not "required" (i.e., a temporary can be created). – Michael Goldshteyn Apr 04 '14 at 16:17
  • 2
    You may use `static constexpr int SOME_VALUE() { return 5; }`... – Jarod42 Apr 04 '14 at 16:17
  • 13
    BTW, you can now give enumerators a well-defined type: `enum : int { SOME_VALUE = 5 };` – Ben Voigt Apr 04 '14 at 16:18
  • 1
    But, enumerators limit us to integral values - another disadvantage to using them. – Michael Goldshteyn Apr 04 '14 at 16:19
  • I don't suppose that `static constexpr int&& SOME_VALUE=27;` is allowed? Probably not... "Such an object shall have literal type" – Ben Voigt Apr 04 '14 at 16:19
  • I think this whole question boils down to "Why does `constexpr` create an lvalue?" – Ben Voigt Apr 04 '14 at 16:28
  • Or perhaps, why does `constexpr` create an lvalue when it's address is never taken (or does not have to be taken, as is the case with `const &` and its ability to make temporaries). – Michael Goldshteyn Apr 04 '14 at 16:37
  • 5
    From similar questions and answers like [[1](http://stackoverflow.com/q/22172789/2644390)], [[2](http://stackoverflow.com/q/14547986/2644390)], [[3](http://stackoverflow.com/q/14547370/2644390)], I have collected [here](http://stackoverflow.com/a/22416899/2644390) a number of workarounds. The most concise is to use `+SOME_VALUE` to get a temporary. – iavr Apr 04 '14 at 16:56
  • @iavr, +SOME_VALUE is an interesting workaround, but again only applicable to numeric types and those for which a unary plus operator is defined. Of course enums only allow integral types as well. – Michael Goldshteyn Apr 04 '14 at 17:17
  • 1
    You could define your own identity function that would force a value to be a temporary: `template constexpr T noref( T t ) { return t; }`. This could be applied by using `noref(SOME_VALUE)` wherever using `SOME_VALUE` ends up taking a reference. (This would only work for literal movable or copyable types, but that's more general than `+` which works for numeric types which are literal copyable types.) – Adam H. Peterson Apr 04 '14 at 19:38
  • The underlying problem is that passing by-reference (potentially) requires an address. There's no address for something that's not defined. So you need to create some object eventually, to get an address. That's why many solutions here use temporaries. Another way to create one is using a wrapper with a `constexpr` conversion operator. I think it should fine if there's no template type deduction involved. – dyp Apr 04 '14 at 20:14
  • 8
    All of these workarounds indicate to me that using an `enum` for integral constants is better than a `static constexpr`. There are fewer surprises (at least one anyway). Bonus for working with older C++ and C, too. – Michael Burr Apr 04 '14 at 21:07
  • 1
    If you consider using +SOME_VALUE, you can also use SOME_VALUE(), and define it as just `static constexpr int SOME_VALUE() { return 5; }`. This is equally general as noref template, and needs less boilerplate. You get bonus of SOME_VALUE having significantly different type than SOME_VALUE(), so you will never forget about adding `()`. – Frax Apr 07 '14 at 12:49
  • 1
    For the record, Clang also compiles the OP's code without the out-of-class definition. – rubenvb Apr 15 '14 at 15:14

6 Answers6

14

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
Community
  • 1
  • 1
TBBle
  • 1,436
  • 10
  • 27
  • 1
    I was going to update with this myself, but since you've done the work, I have decided to just accept your answer to help anyone else looking into this issue. – Michael Goldshteyn Apr 28 '17 at 14:42
9

You have three options here:

  1. 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)

  2. If your class is non-template you can easily put it in source file

  3. Alternatively declare constexpr static member function getSomeValue():

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    
OOO
  • 914
  • 1
  • 10
  • 18
1

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]

david.pfx
  • 10,520
  • 3
  • 30
  • 63
  • @dyp: Thanks! Must have been a late night. See edit. – david.pfx Apr 15 '14 at 23:08
  • The code you show is not enough to determine whether the linkage will be external or not. You need to pass that value by reference to some function as in my example, for the linkage to become external. – Michael Goldshteyn Apr 16 '14 at 15:44
  • @dyp: Ouch! The second sentence was OK. Fixed. – david.pfx Apr 16 '14 at 23:18
  • I don't think *linkage* is actually a problem here. *Linkage* is a property of a name. As I said, binding some static data member `SOME_VALUE` that doesn't have a definition to a reference violates the ODR, which isn't a linkage issue. You can very well use names that do not have linkage (like automatic variables) or internal linkage (like static global variables) as arguments for `push_back`, for example. – dyp Apr 16 '14 at 23:22
  • *"which isn't a linkage issue"* <- that might be a bit confusing, because *the linker* fails to find a definition for the variable in the OP. What I mean here is *linkage* as the visibility of a name, as defined in the Standard. – dyp Apr 16 '14 at 23:27
1

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
  • 2
    This gives the `enum` a type as mentioned in passing by the OP but does not address their primary question, which was whether it's possible to avoid external linkage when using `static const[expr]`' instead of `enum`. – underscore_d Dec 20 '15 at 11:50
  • 2
    OP is explicitly wanting a compile time integer constant, not a distinct type. – Parker Coates Apr 07 '20 at 14:39
0

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

Ophir Gvirtzer
  • 604
  • 4
  • 8
  • 1
    Of course you can, and it won't compile, for exactly the same reason as OP's code. – Frax Apr 07 '14 at 20:10
  • It compiles and works, VS 2012, as it always has (I using this for years). See here http://stackoverflow.com/questions/2605520/c-where-to-initialize-static-const also here http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8l.doc%2Flanguage%2Fref%2Fcplr038.htm and here http://www.devx.com/tips/Tip/5602 – Ophir Gvirtzer Apr 08 '14 at 09:02
  • In general, in C++98 static class members need to be defined in one translation unit, unless they are const – Ophir Gvirtzer Apr 08 '14 at 09:08
  • Well, you are right, it seems that it really _should_ work. But in gcc (tested in 4.8.2) it doesn't. I get `test.cpp:(.text+0x1a): undefined reference to `C::SOME_VALUE'` while linking, just like OP. Seems to be gcc's fault. – Frax Apr 08 '14 at 12:35
  • 3
    The fact that this works in VS2012, does not make it compatible with all other compilers (e.g., gcc). In general, Microsoft has been rather lax in what it's compiler deems to be an error (i.e., an accomodative stance with respect to the developer rather than strict standard compliance). Additionally, if a by value version of push_back is provided in std::vector, as a specialization for integral and/or small POD types, the linker error can be eliminated. Whether or not such an implementation would violate the standard may be open to interpretation. – Michael Goldshteyn Apr 08 '14 at 13:16
  • 1
    Michael, it's the other way around, in this matter VS follows the standard, while gcc doesn't. I found this in the standard 9.4.2/4 - If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions. The member shall still be defined in a namespace scope if it is used in the program and the namespace scope definition shall not contain an initializer. – Ophir Gvirtzer Apr 08 '14 at 14:30
  • The workaround for gcc in this case would be to separate declaration and initialization. Within the class would be `static const int SOME_VALUE;` and external to it would be `const int C::SOME_VALUE = 5;` but that's not much of an improvement. – Edward Apr 08 '14 at 21:41
  • I personally still use enums and if I ever considered something else, it would be to explicitly specify the type, which C++11 allows. The reason why, I would always prefer this, is that I always insist on having a fixed ABI (I always require everything to be accessible and testable from C) – Jens Munk Apr 13 '14 at 17:38
  • 1
    @Ophir, what does that have to do with whether it's an extern or not? – Michael Goldshteyn Apr 16 '14 at 15:42
  • I don't know what happened in six years but this example definitely works in both the latest gcc and clang in C++11/14/17. Unless it was edited to work – JoeManiaci Mar 18 '21 at 19:46
0

Nowadays, the preferred way is:

enum class : int C { SOME_VALUE = 5 };
edwinc
  • 1,658
  • 1
  • 14
  • 14
  • 4
    But now you have to explicitly cast when you need the int value. – Chnossos Apr 24 '14 at 21:02
  • 1
    This gives the `enum` a type as mentioned in passing by the OP but does not address their primary question, which was whether it's possible to avoid external linkage when using `static const[expr]`' instead of `enum`. – underscore_d Dec 20 '15 at 11:51
  • Why would you force a name and a class on the enum? The underlying type I can understand, but why the rest of it? – einpoklum Apr 25 '17 at 19:52