While not being an expert with the clang compiler and checks, I will try to answer with my own experience.
Short answer: No. Your second example (see below) is clearly more robust even if more verbose.
struct DefaultSettings { static constexpr std::string_view defaultIpAddress = "127.0.0.1"; std::string tcpIpAddress = defaultIpAddress; static constexpr uint16_t defaultTcpPort = 1993; uint16_t tcpPort = defaultTcpPort; }
Long answer: Your question actually refers to the issue involved with "magic numbers". Let's consider the following:
void connectToServer(const DefaultSettings &serverSettings) { if (serverSettings.tcpPort == 1993) { /// treat the default port, might be an invalid port with a straight return } else { /// actually connect to the server } } void checkServerConnection(const DefaultSettings &serverSettings) { if (serverSettings.tcpPort == 1993) { /// treat the default port, might be an invalid port with a straight return } else { /// actually connect to the server } }
It is clear that, if your default port configuration is changed for any reason, you need to manually change every line involving the default port. Even for small projects with +10k lines of code it is intractable (at least from my point of view). This "coding style" is highly subject to create future bugs. Therefore, one prefers the following where only one line must be changed if needed:
void connectToServer(const DefaultSettings &serverSettings) { if (serverSettings.tcpPort == DefaultSettings::defaultTcpPort) { /// treat the default port, might be an invalid port with a straight return } else { /// actually connect to the server } } /// and so on...
As a rule of thumb, ALWAYS define hard coded values, e.g char, int, float, and by extension strings into constant declarations. I've already seen people writing my_array.size() == ZERO_SIZE_ARRAY instead of my_array.size() == 0 (in other languages though). This is a trade-off between readability and robustness.
Remark on your code:
I would suggest to not encode the default value directly into the struct/class but rather do:
struct Settings { std::string tcpIpAddress; uint16_t tcpPort; } constexpr const char* DEFAULT_TCP_IP_ADDRESS = "127.0.0.1"; constexpr uint16_t DEFAULT_TCP_PORT = 1993; constexpr Settings DEFAULT_SETTINGS{ DEFAULT_TCP_IP_ADDRESS, DEFAULT_TCP_PORT };
Or something along the line. A struct/class skeleton has the purpose to declare things and not to define them: in other words, a struct/class carries values but does not set them (if we do not consider the methods attached to them, I am a C speaker). This way of coding could also allow you to load dynamically, from a file for example, the default values more easily (but remove the constexpr).
Note:
The reason why clang (and probably other compilers) does not apply an equivalent checks for strings (if this does not exist already), is probably because std::string_view is a class and the compiler does not make the difference between a default constructor std::string tcpIpAddress; and std::string tcpIpAddress = "my specific address" , taking into account the heap allocation. (I might be wrong, others are welcome to correct me).
defaultIpAddressas well]" - yes, except the linter rule only checks numbersdefaultTcpPortanddefaultIpAddressare still magic. Use more descriptive names.<myService>PortandlocalhostIp.readability-magic-numbersis too strict. Leading to too many false positives, leading into people not using the check because it's too restrictive. (or lot's of NOLINT comments, which is also not something you would want)