'c++ constexpr concatenate char*

Context: In my company we generate a lot of types based on IDL files. Some of the types require special logic so they are handcoded but follow the same pattern as the generated ones. We have a function which all types must implement which is a name function. This will return the type name as a char* string and the function is constexpr.

Problem: The problem is regarding collections which could contain other collections nested potentially N number of times. I therefore am trying to concatenate two or more char* strings at compile time.

Pseudocode of what I want to achieve:

template <typename T>
constexpr char* name()
{
    constexpr char* collectionName = "std::vector";
    constexpr char* containedTypeName = name<T>();
    return concat(collectionName, "<", containedTypeName, ">");
}

Note: There are examples out there which does something like this but is done with char[] or the use of static variables.

The question: How can I make a constexpr function which return a char* which consists of two or more concatenated char* strings at compile time? I am bound to C++17.



Solution 1:[1]

From constexpr you cannot return char* which is constructed there... You must return some compile time known(also its size) constant thingy. A possible solution could be something like:

#include <cstring>

// Buffer to hold the result
struct NameBuffer
{
    // Hardcoded 128 bytes!!!!! Carefully choose the size!
    char data[128];
};

// Copy src to dest, and return the number of copied characters
// You have to implement it since std::strcpy is not constexpr, no big deal.
constexpr int constexpr_strcpy(char* dest, const char* src);

//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr NameBuffer name()
{
    // We will return this
    NameBuffer buf{};

    constexpr const char* collectionName = "std::vector";
    constexpr const char* containedTypeName = "dummy";

    // Copy them one by one after each other
    int n = constexpr_strcpy(buf.data, collectionName);
    n += constexpr_strcpy(buf.data + n, "<");
    n += constexpr_strcpy(buf.data + n, containedTypeName);
    n += constexpr_strcpy(buf.data + n, ">");
    // Null terminate the buffer, or you can store the size there or whatever you want
    buf.data[n] = '\0';
    return buf;
}

Demo

And since the returned char* is only depends on the template parameter in your case, you can create templated variables, and create a char* to them, and it can act like any other char*...

EDIT:

I have just realized that your pseudo code will never work!! Inside name<T>() you are trying to call name<T>().
You must redesign this!!! But! With some hack you can determine the size at compile time somehow for example like this:

#include <cstring>
#include <iostream>

template<std::size_t S>
struct NameBuffer
{
    char data[S];
};

// Copy src to dest, and return the number of copied characters
constexpr int constexpr_strcpy(char* dest, const char* src)
{
    int n = 0;
    while((*(dest++) = *(src++))){ n++; }
    return n;
}

// Returns the len of str without the null term
constexpr int constexpr_strlen(const char* str)
{
    int n = 0;
    while(*str) { str++; n++; }
    return n;
}

// This template parameter does nothing now...
// I left it there so you can see how to create the template variable stuff...
//note: in c++20 make it consteval not constexpr
template <typename T>
constexpr auto createName()
{
    constexpr const char*  collectionName = "std::vector";
    constexpr const char* containedTypeName = "dummy";

    constexpr std::size_t buff_size = constexpr_strlen(collectionName) + 
                                      constexpr_strlen(containedTypeName) +
                                      2; // +1 for <, +1 for >

    /// +1 for the nullterm
    NameBuffer<buff_size + 1> buf{};

    /// I'm lazy to rewrite, but now we already calculated the lengths...
    int n = constexpr_strcpy(buf.data, collectionName);
    n += constexpr_strcpy(buf.data + n, "<");
    n += constexpr_strcpy(buf.data + n, containedTypeName);
    n += constexpr_strcpy(buf.data + n, ">");
    buf.data[n] = '\0';
    return buf;
}

// Create the buffer for T
template<typename T>
static constexpr auto name_buff_ = createName<T>();

// point to the buffer of type T. It can be a function too as you wish
template<typename T>
static constexpr const char* name = name_buff_<T>.data;

int main()
{
    // int is redundant now, but this is how you could use this
    std::cout << name<int> << '\n';
    return 0;
}

Demo

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1