GetDunne Wiki

Notes from the desk of Shane Dunne, software development consultant

User Tools

Site Tools


constexpr_instead_of_preprocessor_define

Using C++ "constexpr" to replace #define

In C, the only way to define symbolic constants was to use #define—in effect, a parameter-less macro. This is often used to specify the value of a symbolic constant in one place, so it can be used later in one or more other places, to declare the size of an array, e.g.

#define kMyArraySize 100
...
double MyArray[kMyArraySize];

C++ introduced the const keyword, which is a big improvement because it allows you to declare strongly-typed symbolic constants, e.g.

const int MyArraySize = 100;

The expression to the right of the = (assignment operator, used as an initialization operator in this context) in such a const declaration is computed at compile-time by the C++ compiler, and as a result can be used in an array-size declaration like this:

double MyArray[kMyArraySize];

Within a class declaration, it's necessary to declare such compile-time const objects static, e.g.

class MyClass
{
    static const int kArraySize = 100;
    ...
    double array[kArraySize];
};

I'm not entirely sure why this is, because if the declared constant is indeed constant, defined at compile time, there would never be a need for it to be treated as an instance member. However, the people who write the compilers make the rules, and this is simply the rule.

In my original VanillaJuceAudioProcessor class declaration, I used an awkward combination of a #define for a fixed array size, plus an enum declaration for a constant used in the constructor, like so:

#define kNumberOfPrograms 128
 
class VanillaJuceAudioProcessor
    : public AudioProcessor
    , public ChangeBroadcaster
{
public:
    enum { maxNumberOfVoices = 16 };
    ...
private:
    SynthParameters programBank[kNumberOfPrograms];
    ...
};
 
VanillaJuceAudioProcessor::VanillaJuceAudioProcessor()
    : currentProgram(0)
{
    ...
    for (int i = 0; i < maxNumberOfVoices; ++i)
        synth.addVoice(new SynthVoice());
    ...
}

In the first case I was just using #define the way I always had in C, and in the second case I was following an example I'd seen somewhere, but the truth was I just hadn't figured out how to do it properly in C++, which is

class VanillaJuceAudioProcessor
    : public AudioProcessor
    , public ChangeBroadcaster
{
    ...
private:
    static const int kNumberOfPrograms = 128;
    static const int kNumberOfVoices = 16;
    ...
    SynthParameters programBank[kNumberOfPrograms];
    ...
};

The C++11 standard introduced a more powerful new keyword constexpr, which can be used to define complex expressions to be evaluated at compile-time, and which is particularly useful with templates. I hunted across the web to find any evidence that constexpr should be used for ordinary constant-declarations, without success, so I'm not sure I understand Jules's admonishment “NEVER use a #define for a constant!! Use constexpr!”.

I did find one instance in the JUCE source code, where constexpr was used in this way, in the file juce_android_OpenSL.cpp:

class SLRealtimeThread
{
public:
    static constexpr int numBuffers = 4;
    ...
};

Since the class SLRealtimeThread is not a template class, I have to assume that constexpr is trivially equivalent to const in this case, and that Jules was using “constexpr” somewhat generically as a short form of “const expression''.

constexpr_instead_of_preprocessor_define.txt · Last modified: 2017/09/01 17:34 by shane