Showing posts with label constant. Show all posts
Showing posts with label constant. Show all posts

Saturday, 12 March 2022

Using Const Char* in Template

Compile Time Template Strings.... Yeah, these are a thing and I'm going to show you a live example of how to do them.  First lets take a moment to just understand what we want in some C++ pseudo code.

template<typename T>
T Square(const T value)
{
   return value * value;
}

const auto result = Square<int>(2);

const auto result = Square<double>(2.42);

In a template like this we can see this requires a number and more advanced writers might use SFINAE and type traits to ensure that the type is an arithmetic type, this would stop us doing something like...

const auto result = Square<"Hello">(2);

In this case we can not even compile, not because "Hello" is not operable with the multiplication, but that it's not that, the issue is that a const char* isn't allowed in the template itself.  You can't define a typename as const char*... Or can you?

Well, it seems you can in C++20.  The idea I had was to accept the template but treat it like a decaying length, copying the buffer in the template each step.  How could I do this?  Well, I need to compare the template somehow and determine the length of "Hello"... The buffer containing the data "Hello\0" itself is there in the code, what I needed was to think about how to treat that as type 6, the length...

The spaceship operator is what sprang to mind, but then I had the issue that the comparison result type wasn't known.... At least I don't know it, I don't know what string literal maybe being passed in, how to know it?  Well I didn't need to, I just return auto!

Here is the key code....  

  template<std::size_t Length>
  struct TemplateString
  {
    // Constructor is constexpr and simply copies the payload text
    // into the member data setup
    constexpr TemplateString(const char(&pPayloadText)[Length + 1])
    {
      std::copy_n(pPayloadText, Length+1, mText);
    }
    
    auto operator<=>(const TemplateString&) const = default;
    
    // Default is just an empty buffer of the Length+1
    char mText[Length+1] = {};
  };
  
  // Make decaying the template Length possible
  template<std::size_t Length>
  TemplateString(const char(&str)[Length])->TemplateString<Length-1>;


The template string is simply copying the buffer given locally, and the unwinding is stopped when the template length is met in the comparator.  I think this is a little strange, I may even be wrong (probably am) in explaining how this unrolling works.

However, what use do I have for this?

Well, my main use is in parameterising on the template the NAME of a thing... I have long seen code where the onus is on the engineer to put some static constant string into their derived class to allow some other feature to know what your class is called, or what it does.

Systems, Events, Services... These sort of things, general things that need a name for us mere humans all introduce this pattern of:

Base Class --> Derived Class :: sName

Where this sName is brittle, it can be forgotten, it can be mangled, it can be copy I paste... All sorts of issues with it.  I personally have my statemachine code up on github and I use an event within that, which could sorely use this very string trick... so I will be using it.

Lets take a look at the code in action on CompilerExplorer here.

And you can get hold of my personal example on github here.

As ever, if you liked this, subscribe on YouTube to let me know, comment below and if you use this trick feel free to buy me a coffee with the tip jar up top of this page.

Monday, 8 May 2017

Software Development : Get Constant in C++

What does "const" mean to you?  Does it just mean a value can not be changed?  If so, you may want to read on!

Const is one of those things, which though not unique to C++ is given more meaning when you leverage C++ fully, const allows you to not only define a value as invariant, but also to instruct other programmers coming to your code how a value should be used, how when it is passed as a parameter it should be treated and ultimately how to protect data from needless alteration, de-synchronisation or simple corruption.

Const is therefore your friend, and if you've come to C++ from C, Java, C#, Python or one of the other myriad of languages which don't treat const with as much relevance as C++ you may want to read more than I can say on the topic. 

Bjarne Stroustrop (the inventor of C++) and other authors on the topic (notably Scott Meyers & Herb Sutter) explain in much more detail than I ever could, but for brevity here are two examples of const from my own coding standard which I implore you to digest.

1. Initialise Const Values in the constructor ONLY....

Any instance of the "Ex" class can now access it's "Pi" value, and we communicate to all viewers that the value is a constant and only edited in one place.

2. Where-ever possible pass values as constant references....

We know for certain that the "p_Radius" parameter being passed is NOT to be changed by the function, this is important when you are thinking about letting code document itself, and easily accomplished with "const", and especially annoying when using a language lacking const!

Whatever you do, use const consistently, this needs no specific explanation here, however if you begin following my second rule, you must stick to it!  Don't change half way through a program, if you import a third party API which is not using const in the same way, abstract that third party interface away with a series of your own shims, to make it correct for your usage (the compiler will optimise all these layers away) to leave you with consistent and more easily maintainable code.

You can find must much more about const-correctness elsewhere, I'd start here.