Place strings in template parameters

    Modern C ++ has brought us a bunch of features that were not enough in the language before. In order to somehow get a similar effect for a long time, stunning crutches were invented, mainly consisting of very large patterns and macros (often auto-generated ones). But now from time to time there is a need for opportunities that are not yet in the language. And we begin to reinvent complex designs from templates and macros, generate them and achieve the behavior we need. This is just such a story.

    Over the past half-year, I needed the values ​​that could be used in the template parameters twice. At the same time, I wanted to have human-readable names for these values ​​and eliminate the need to declare these names in advance. Specific tasks that I solved are a separate question, maybe later I will write separate posts about them, somewhere in the “abnormal programming” hub. Now I will talk about the approach by which I solved this problem.

    So, when it comes to template parameters, we can use either a type or a static const value. For most tasks, this is more than enough. We want to use human-readable identifiers in the parameters - we declare the structure, enumeration or constant and use them. Problems begin when we cannot determine this identifier in advance and want to do it on the spot.

    It would be possible to declare the structure or class directly in the template parameter. It will even work if the template does not do something with this parameter, which requires a complete description of the structure. In addition, we cannot control the namespace in which such a structure is declared. And completely identical-looking template substitutions will turn into completely different code if these lines are in neighboring classes or namespaces.

    You need to use literals, and of all the literals in C ++, only a character literal and a string literal can be called readable. But the character literal is limited to four characters (when using char32_t), and the string literal is an array of characters and its value cannot be passed to the template parameters.

    It turns out some kind of vicious circle. You need to either declare something in advance, or use uncomfortable identifiers. Let's try to get from the language of what it is not adapted. What if you implement a macro that makes a string literal something suitable for use in template arguments?

    Make a structure for the string


    First, let's make the basis for the string. In C ++ 11, variadic template arguments appeared.
    We declare a structure that contains the characters of the string in the arguments:

    template <char... Chars>
    structString{};
    

    github

    It works. We can even immediately use these lines like this:

    template <classT>
    structFoo {};
    Foo<String<'B', 'a', 'r'>> foo;
    

    And now we will carry this line into runtime.


    Fine. It would not be bad to be able to get the value of this line in runtime. Let there be an additional template structure that will extract the arguments from such a string and make a constant of them:

    template <classT>
    structGet;template <char... Chars>
    structGet<String<Chars...>> {staticconstexprchar value[] = { Chars... };
    };
    

    It works too. Since the strings do not contain '\ 0' at the end, it is necessary to operate with this constant accurately enough (in my opinion, it is better to immediately create a string_view using a constant and sizeof from it in the constructor arguments). One could simply add a '\ 0' at the end of the array, but for my tasks this is not necessary.

    Check that we can manipulate such strings.


    Okay, what else can you do with such strings? For example, concatenate:

    template <classA, classB>
    structConcatenate;template <char... Chars, char... ExtraChars...>
    structConcatenate<String<Chars...>, String<ExtraChars...>> {using type = String<Chars..., ExtraChars...>;
    };
    

    github

    In principle, it is possible to do any operation more or less (I have not tried it, since I don’t need it, but I can just imagine how you can search for a substring or even replace a substring).
    Now we have the main question: how to compile-time to extract characters from a string literal and put them into the template arguments.

    We finish the owl. We write macro


    Let's start with the way to put the characters in the template arguments one by one:

    template <classT, charc>
    structPushBackCharacter;template <char... Chars, char c>
    structPushBackCharacter<String<Chars...>, c> {using type = String<Chars..., c>;
    };
    template <char... Chars>
    structPushBackCharacter<String<Chars...>, '\0'> {using type = String<Chars...>;
    };
    

    github

    I use a separate specialization for the '\ 0' character to not add it to the used string. In addition, it somewhat simplifies other parts of the macro.

    The good news is that the string literal can be a parameter to the constexpr function. Let's write a function that returns the character by the index in the string or '\ 0', if the string length is less than the index (this is where the PushBackCharacter specialization for the '\ 0' character comes in handy).

    template <size_t N>
    constexprcharCharAt(constchar (&s)[N], size_t i){
      return i < N ? s[i] : '\0';
    }
    

    Basically, we can already write something like this:

    PushBackCharacter<
      PushBackCharacter<
        PushBackCharacter<
          PushBackCharacter<
            String<>,
            CharAt("foo", 0)
          >::type,
          CharAt("foo", 1)
        >::type,
        CharAt("foo", 2)
      >::type,
      CharAt("foo", 3)
    >::type
    

    We put such a footcloth, but really (we can write scripts to generate code) inside our macro, and that’s it!

    There is a nuance. If the number of characters in the string is greater than the nesting levels in the macro, the line will simply be cut off and we will not even notice. Disorder.

    We will create another structure that does not convert the string entered into it, but makes static_assert that its length does not exceed the constant:

    #define _NUMBER_TO_STR(n) #n#define NUMBER_TO_STR(n) _NUMBER_TO_STR(n)template <classString, size_tsize>
    structLiteralSizeLimiter {using type = String;
      static_assert(size <= MAX_META_STRING_LITERAL_SIZE,
          "at most " NUMBER_TO_STR(MAX_META_STRING_LITERAL_SIZE)
          " characters allowed for constexpr string literal");
    };
    #undef NUMBER_TO_STR#undef _NUMBER_TO_STR

    Well, the macro will look something like this:

    #define MAX_META_STRING_LITERAL_SIZE 256#define STR(literal) \
      ::LiteralSizeLimiter< \
        ::PushBackCharacter< \
        ... \
        ::PushBackCharacter< \
          ::String<> \
        , ::CharAt(literal, 0)>::type \
          ... \
        , ::CharAt(literal, 255)>::type \
        , sizeof(literal) - 1>::type
    

    github

    Happened


    template <classS>
    std::string_view GetContent(){
      returnstd::string_view(Get<S>::value, sizeof(Get<S>::value));
    }
    std::cout << GetContent<STR("Hello Habr!")>() << std::endl;
    

    The implementation that came out of me can be found on the githaba .

    I would be very interested to hear about the possible applications of this mechanism, other than those that I invented.

    Also popular now: