Home > Mobile >  constexpr string literals as identifiers
constexpr string literals as identifiers

Time:01-23

From time to time I like to use static const char*/static const char[] as identifiers instead of enums. The reliability of the string literal pointer is nice.

Lately I have been wondering, can one rely on a constexpr method to return an identical pointer every time? Specifically:

struct traits_like
{
   static constexpr const char* id() { return "special traits"; }
}

Also, would something like constexpr const char* id = "special traits" or constexpr char[] id be a better practice in this case?

UPDATE:

In my use case, I'd have many traits-like classes flying around that I find via templating, so one would see in theory something like TTraits1::id() == TTraits2::id()

CodePudding user response:

The resolution of CWG 1823 clarifies that repeated evaluation even of the same string literal does not have to produce the same string literal object. In particular your inline function doesn't have to return the same pointer address in different translation units or in repeated calls.

Furthermore, multiple string literal objects may use overlapping storage. So two pointers to string literals being equal also doesn't imply that they have the same value.

CodePudding user response:

As it comes to better practice, I would suggest using std::string_view, for example:

struct A {
    constexpr static std::string_view name = "A";
};

It is a clean and simple way, what is more - gives you a convenient interface.

A more complete example:

#include <iostream>
#include <concepts>
#include <string_view>

struct A {
    constexpr static std::string_view name = "A";
};

struct B {
    constexpr static std::string_view name = "B";
};

struct OtherStruct {
    constexpr static std::string_view name = "OtherStruct";
};

template <typename T>
concept struct_with_name = requires {
    { T::name } -> std::same_as<const std::string_view&>;
};

template <struct_with_name T>
void print_struct([[maybe_unused]] const T& input) {
    std::cout << "Name: " << T::name << "\n";
}

int main()
{
    print_struct(A{});
    print_struct(B{});
    print_struct(OtherStruct{});
}

Expected result:

Name: A
Name: B
Name: OtherStruct

Passing a struct with no name will result in a compilation error:

(...)
<source>:23:6: note: constraints not satisfied
<source>: In substitution of 'template<class T>  requires  struct_with_name<T> void print_struct(const T&) [with T = NoName]':
(...)
<source>:19:10: note: the required expression 'T::name' is invalid
   19 |     { T::name } -> std::same_as<const std::string_view&>;

In my use case, I'd have many traits-like classes flying around that I find via templating, so one would see in theory something like TTraits1::id() == TTraits2::id()

Using the elements from the example above, you can simply do this:

if constexpr (A::name == B::name) {
    std::cout << "The same...\n";
} else {
    std::cout << "Not the same...\n";
}
  •  Tags:  
  • c
  • Related