4

I am looking for a portable one line replacement for the #define in the following code. The replacement should hide the word APPLE in the namespace of the Foo object.

class Foo {
public:
#define APPLE 123
    Foo(int a) : a_(a) { }
};

 // elsewhere in another file
    Foo f(APPLE);

I tried to make this more C++ friendly this, and it worked using the Intel 2017 compiler:

class Foo {
public:
    static constexpr int APPLE = 123;
    Foo(int a) : a_(a) { }
};

// elsewhere

    Foo a(Foo::APPLE);

but it does not work with g++ ((GCC) 6.3.1 20170216), because it gives the error

undefined reference to Foo::APPLE

because it is probably trying to take a reference to APPLE.

I know I can "fix" the problem by creating definition in a *.cpp file of

constexpr int Foo::APPLE;

but that violates my ideal of having the #define be replaced by 1 line. My Foo class is header-file only, and now I would need a cpp file just for the definition of Foo::APPLE. I know I could also declare APPLE as a function (static constexpr int APPLE() {return 123;}) but that is a whole lot more typing in the declaration and at every point of use I need to call the function with ().

It seems easier just to use the #define and be done with it. Non-static const int works fine, but APPLE is not usable as an argument to the constructor. Maybe there is a good reason why this is impossible in C++.

Edit: This is not a duplicate of Undefined reference to static constexpr char[]. That question is related to a string and why the particular error message comes up. I am trying to avoid using static linkage all together (I acknowledge in my question that I am aware of how to do static linkage) and I want to do it a "better/cleaner" way, and I see from the Answers that the way that passes my criteria is to use enum.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
  • 1
    Can you use C++17? C++17 has inline variables for this reason. – Alex Huszagh Oct 18 '18 at 23:35
  • 2
    C style? `enum { APPLE = 123 };` – melpomene Oct 18 '18 at 23:35
  • @AlexanderHuszagh Can't use C++17. Intel doesn't fully support it yet, and we need to use Intel compiler. – Mark Lakata Oct 18 '18 at 23:40
  • @melpomene Well, the enum worked. I've gotten to used to C++11 enum classes and was worried it would generate a warning due a type conflict (which we promote to errors). But it didn't create a warning. – Mark Lakata Oct 18 '18 at 23:42
  • 1
    @MarkLakata Then the enum is likely your best option unfortunately. Not ideal, but it works. – Alex Huszagh Oct 18 '18 at 23:49
  • 1
    Possible duplicate of [Undefined reference to static constexpr char\[\]](https://stackoverflow.com/questions/8016780/undefined-reference-to-static-constexpr-char) – IdeaHat Oct 18 '18 at 23:50
  • Possible duplicate of [static const Member Value vs. Member enum : Which Method is Better & Why?](https://stackoverflow.com/q/204983/608639) and [Why should I prefer static constexpr int in a class over enum for class-level integral constants?](https://stackoverflow.com/q/35213098/608639) – jww Oct 19 '18 at 03:39
  • 1
    `static constexpr int APPLE = 123;` requires a storage allocation and takes up space. You can take the address of `APPLE`. Use an `enum` instead. It is effectively a symbolic constant (but you can't take the address of it). – jww Oct 19 '18 at 03:41

2 Answers2

6

You've already listed most alternatives in your question. You'll need consider which approach you want to take:

  • Use inline variable which requires C++17 (your first attempt works implicitly in this standard)
  • Define the static member in a source file, which you don't want to do
  • Use an inline static member function instead, which you also don't want
  • Use a namespace scoped constexpr variable instead of a member
  • Use a member enum: enum : int { APPLE = 123 };
  • Use the macro (don't pick this)
eerorika
  • 232,697
  • 12
  • 197
  • 326
2

In addition to the things mentioned already, there's also "poor man's inline variables":

static constexpr const int& APPLE = std::integral_constant<int, 123>::value;

You define a class template with a constant static data member whose value is what you want. You define that static data member out-of-line - but in the header, since it's a static data member of a class template. In this case, std::integral_constant does all that already, so you don't have to write your own.

Then, you define your actual static data member constant as a constexpr reference to that class template static data member; no out-of-line definition is needed because it is not possible to odr-use a reference initialized by a constant expression.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Thanks for the answer. I was not aware of the existence of `integral_constant` and the fact that template exists at all doesn't help me like C++ any more. What problem was that intending to solve? (I understand it sort of solves this problem, but doesn't this put a huge burden on the optimizer to figure out that APPLE can be treated as a literal? – Mark Lakata Oct 20 '18 at 23:33
  • `integral_constant` is primarily used in template metaprogramming to encode a compile-time constant into a type. Using it here is convenient, but it's hardly its intended use. This is trivial constant propagation that the compiler should have little problems with. – T.C. Oct 20 '18 at 23:50