Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

constexpr conversion of hex chars to std::string

I have a number of strings like this:

"343536"_hex

that I would like to convert into their corresponding byte strings. I am using C++11and have defined a user-defined string literals to convert these into hex strings. However, the conversion I currently have cannot be evaluated as a constexpr which is what I'm seeking. In particular I would like to use something like this, but as a constexpr:

std::string operator "" _hex(const char *s, std::size_t slen )
{
    std::string str;
    str.reserve(slen);
    char ch[3];
    unsigned long num;
    ch[2] = '\0';

    for ( ; slen; slen -= 2, s += 2) {
        ch[0] = s[0];
        ch[1] = s[1];
        num = strtoul(ch, NULL, 16);
        str.push_back(num);
    }
    return str;
}

Test driver

int main()
{
    std::string src{"653467740035"_hex};
    for (const auto &ch : src)
        std::cout << std::hex << std::setw(2) << std::setfill('0') 
                  << (unsigned)ch << '\n';
}

Sample output

65
34
67
74
00
35

The question

To be very, very clear about what I'm asking, it's this: How can I write a C++11 string literal conversion of this type that can be evaluated at compile time as a constexpr?

like image 904
Edward Avatar asked Aug 07 '14 04:08

Edward


1 Answers

In order to achieve what you are trying to do, you will need to have some compile-time string class that is compatible with constexpr. There isn't such a standard thing though. I can see some things that approach it:

  • boost::mpl::string, but the interface is not really pretty.
  • boost::log::string_literal, which has the interface you want but lacks the constexpr support.
  • std::string_literal, which is exactly what you are looking for, but which isn't implemented. It can probably be implementable in C++11 though if you have some free time.

In order to simplify all of this, let's use an overly simplified string_literal class. Note that some of the classes described above have a trailing \0 to be closer to std::string, but we won't bother to add one.

template<std::size_t N>
struct string_literal
{
    char data[N];
};

We will also provide operator+ for concatenation. It would take some time to explain how it works and it's not really relevant for the question. Let's say that it is just some template wizardry (std::integer_sequence is a C++14 utility but can be implemented in C++11):

template<std::size_t N1, std::size_t N2, std::size_t... Ind1, std::size_t... Ind2>
constexpr auto concatenate(string_literal<N1> lhs, string_literal<N2> rhs,
                   std::index_sequence<Ind1...>, std::index_sequence<Ind2...>)
    -> string_literal<N1+N2>
{
    return { lhs.data[Ind1]... , rhs.data[Ind2]... };
}

template<std::size_t N1, std::size_t N2>
constexpr auto operator+(string_literal<N1> lhs, string_literal<N2> rhs)
    -> string_literal<N1+N2>
{
    using Indices1 = std::make_index_sequence<N1>;
    using Indices2 = std::make_index_sequence<N2>;
    return concatenate(lhs, rhs, Indices1{}, Indices2{});
}

You can use the template user-defined literal (with char...) to get rid of the string literal and have a prettier literal (1234_hex instead of "1234"_hex):

template<char... Chars>
auto operator "" _hex()
    -> string_literal<sizeof...(Chars)/2>
{
    return process<Chars...>();
}

Now, all you need is a function that can process your characters by pairs. A generic one and an overload for the "finish" condition. Note that the enable_if_t is needed to avoid ambiguous function calls (that's C++14, but you can replace it by typename std::enable_if<...>::type in C++11). The "real" work of converting the characters to the equivalent numbers is done in the process overload that only takes two template arguments.

template<char C1, char C2>
constexpr auto process()
    -> string_literal<1>
{
    return { 16 * (C1 - '0') + (C2 - '0') };
}

template<char C1, char C2, char... Rest,
         typename = std::enable_if_t< (sizeof...(Rest) > 0), void >>
constexpr auto process()
    -> string_literal<sizeof...(Rest)/2 + 1>
{
    return process<C1, C2>() + process<Rest...>();
}

You could add many more checks to ensure that there is always an even number of characters or to ensure that there aren't any bad characters. The code I provided uses some features from the C++14 standard library, but I made sure to only use features that can be easily reimplemented in C++11 if needed. Note that you could probably write a more human-readable program with C++14 thanks to the relaxed restrictions on constexpr functions.

Here is a working C++14 example with all the aforementioned functions and classes. I made sure that your test program still works (I just replaced scr by scr.data in the loop since we use an edulcorated string_literal class).

like image 138
Morwenn Avatar answered Sep 19 '22 01:09

Morwenn