Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using static const structs to group related class constants (C++11)

Tags:

c++

c++11

What are the (dis)advantages of using the following (A):

// .h
class SomeClass
{
    static const struct ConstantGroup
    {
        int a = 1;
        string b = "b";
        // ... etc.
    } CONSTANT;
};
// .cpp
const SomeClass::ConstantGroup SomeClass::CONSTANT;

Versus (B):

// .h
class SomeClass
{
    static const int A;
    static const string B;
    // .. etc.
};
// .cpp
const int SomeClass::A = 1;
const string SomeClass::B = "b";

...for some group(s) of related static class constants? Assume no templates are involved and that the constants contain simple types (POD or strings).

So far I see the following advantages in favor of (A):

  • Related constants can be passed around as a group EDIT: As was pointed out in the comments, this is not generally desired.
  • Given that the constants are often accessed together, we can create shorthands for the structure to improve readability when needed, i.e.: static const auto & SHORTHAND = SomeClass::LONG_NAME_FOR_CONSTANTS;

What are the disadvantages, gotcha's, or other things to keep in mind when using this pattern?

like image 554
Stargazer Avatar asked Jan 27 '23 20:01

Stargazer


2 Answers

(A) might be harder to optimize by removing unnecessary variables from the final executable.

If you want to group constants, then consider using a namespace for that purpose.

namespace ConstantGroup
{
    constexpr int a = 1;

    // Here best solution might depend on usage and c++ version
    const std::string b;    
}

Passing constants as a group really does not make much sense. If something is really constant, then you need a single definition and always use it.

Also if the constant is very specific to one class, then make it a (static) member of that class.

like image 91
Phil1970 Avatar answered Jan 30 '23 09:01

Phil1970


Interesting (following on from a conversation in the comments above with @Henri Menke about strings and string_views).

Given this:

#include <string>
#include <string_view>
#include <iostream>

static const std::string a = "a";
static const std::string_view b = "b";

int main ()
{
    std::cout << a << "\n";
    std::cout << b << "\n";
}

You can clearly see at Godbolt that constructing a requires a runtime initialiser whereas b is a compile-time constant.

If you don't [like] read[ing] the code generated by the compiler, then try changing both consts to constexprs. Then, std::string_view still compiles but std::string does not.

So, for static and / or global constant strings, constexpr std::string_view = "blah blah blah"; looks to be a good solution here as Henri says because it offers quite a bit of extra functionality over a good old-fashioned C-string, IF you can use C++17 AND you don't mind the cost of converting these to std::strings (which will involve constructing one) in contexts where that is what is needed at that point in the code.

If not, you are forced back to std::string or perhaps plain old C-strings.


Edit:

I noticed a strange shortcoming in std::stringview while looking into this: it offers no operator std::string () method. I've no idea why not but it means, for example, that the following won't compile:

void foo (std::string s)
{
    ...
}

std::string_view sv = ...;
foo (sv);

This is not good enough say I, so in a spirit of sharing (if anyone's still reading at this point), I humbly offer you this:

#include <string>
#include <string_view>

template <class T> struct MyBasicStringView : public std::basic_string_view <T>
{
    constexpr MyBasicStringView (const T *s) : std::basic_string_view <T> (s) { }
    operator std::basic_string <T> () const { return std::basic_string <T> (this->data ()); }
};

using MyStringView = MyBasicStringView <char>;

Test program:

static constexpr MyStringView a_static_string_view = "static_string";

std::string foo (std::string s)
{
    return s + " x";
}

#include <iostream>

int main ()
{
    std::cout << a_static_string_view << "\n";
    MyStringView sv = "abcde";
    std::cout << sv << "\n";
    std::cout << foo (sv) << "\n";
}

Output:

static_string
abcde
abcde x

Live demo.

like image 20
Paul Sanders Avatar answered Jan 30 '23 08:01

Paul Sanders