1

I'm trying to create a program that can do conversions from a decimal number to a hexidecimal number at compile time. Unfortunately I'm new to metaprogramming and I can't figure out a way to do this at compile time. Any ideas how I can approach this?

How can I represent such a number without violating the principles of template metaprogramming.

  • 2
    Do you mean, to obtain a string with the hex digits? – Cheers and hth. - Alf Feb 22 '15 at 15:34
  • That can be a possibility. I just want to calculate the hex value and store it in some sort of representation suitable for metaprogramming. I also made another program in which I did a binary conversion, but I don't see how that helps me here. (I used an enum in a struct to hold the value. ) – Daemonstool Feb 22 '15 at 15:39

2 Answers2

2

The general problem of constructing the base B numeral that represents a given unsigned integer at compiletime is quite interesting. It can be solved concisely by an application of the valuable technique of building a compiletime std::array, which goes back at least to this answer

The function template digits as defined below returns a compiletime null-terminated std::array of char that contains the base Base numeral representing the integer N, for Base in the reasonable range [2,36] (digits '0-9A-Z')

#include <type_traits>
#include <array>

// Get the number of base `base` digits in `n`
constexpr std::size_t base_digits(unsigned long n, unsigned base) 
{
    return (n / base) == 0 ? 1 : 1 + base_digits(n / base,base);
}

// Convert the remainder `r` of division by some base to a based digit
constexpr char based_digit(unsigned r)
{
    return r > 36 ? '!' : r < 10 ? '0' + r : 'A' + r - 10;
}

template <unsigned long N, unsigned Base, 
    std::size_t Len = base_digits(N,Base) + 1, unsigned ...Rems>
constexpr std::array<char, Len> 
digits(std::enable_if_t<(Base > 1 && Base <= 36 && N < Base)> * = nullptr)
{
    return std::array<char,Len>{{based_digit(N), Rems...,0}};
}

template <unsigned long N, unsigned Base, 
    std::size_t Len = base_digits(N,Base) + 1, unsigned ...Rems>
constexpr std::array<char,Len> 
digits(std::enable_if_t<(Base > 1 && Base <= 36 && N >= Base)> * = nullptr)
{
    return digits<N / Base, Base, Len, based_digit(N % Base), Rems...>();  
}

Prepend that to this test:

constexpr auto decimal = digits<9876543210,10>();
constexpr auto hex = digits<9876543210,16>();
constexpr auto octal = digits<123456789,8>();
constexpr auto binary = digits<123456789,2>();
constexpr auto b_36 = digits<36 + 35,36>();

static_assert(decimal.size() == 11,"");

#include <iostream>
#include <cassert>

int main() {
    char * pend;    
    assert(std::strtoul(decimal.data(),&pend,10) == 9876543210); 
    std::cout << decimal.data() << std::endl;
    assert(std::strtoul(hex.data(),&pend,16) == 9876543210);
    std::cout << hex.data() << std::endl;
    assert(std::strtoul(octal.data(),&pend,8) == 123456789);
    std::cout << octal.data() << std::endl;
    assert(std::strtoul(binary.data(),&pend,2) == 123456789);
    std::cout << binary.data() << std::endl;
    assert(std::strtoul(b_36.data(),&pend,36) == 36 + 35);
    std::cout << b_36.data() << std::endl;
    return 0;
}

Output is:

9876543210
24CB016EA
726746425
111010110111100110100010101
1Z

(gcc 4.9.2/clang 3.5.1, -std=c++14. Trivially adaptable for C++11)

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
1
#include <algorithm>        // std::reverse
#include <iterator>         // std::begin, std::end
#include <iostream>
#include <tuple>
#include <type_traits>      // std::integral_constant

namespace detail {
    using std::tuple;
    using std::tuple_cat;
    using std::integral_constant;

    template< unsigned x, int n_digits >
    struct Hex_tuple_
    {
        using Type = decltype( tuple_cat(
            typename Hex_tuple_< x & ((1 << 2*n_digits) - 1), n_digits/2 >::Type(),
            typename Hex_tuple_< (x >> 2*n_digits), n_digits/2 >::Type()
            ) );
    };

    template< unsigned x >
    struct Hex_tuple_< x, 1 >
    {
        using Type = tuple< integral_constant< int, x > >;
    };

}  // namespace detail

template< unsigned x >
using Hex_ = typename detail::Hex_tuple_< x, 2*sizeof( x ) >::Type;

template< int... digit >
auto operator<<(
    std::ostream& stream,
    const std::tuple< std::integral_constant< int, digit >... >&
    )
    -> std::ostream&
{
    int digits[] = { digit... };
    std::reverse( std::begin( digits ), std::end( digits ) );
    for( int x : digits )
    {
        stream << "0123456789ABCDEF"[x];
    }
    return stream;
}

auto main() -> int
{
    std::cout << Hex_<123456>() << "\n";
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • I like your solution but I can't see how the digit pack is generated with the correct values (0 to F) Can you explain more about the typename Hex_tuple? Lets say I want to make it more generic and rewrite it for every base I can imagine. Can I still use this approach? @Cheers – Daemonstool Feb 22 '15 at 21:10
  • @Daemonstool: Hex (that you asked for) is a very special case. You generally want 2n hex digits for an n byte value regardless of the magnitude of that value (assuming 8-bit bytes). And you want special treatment of negative values, as two's complement (in general). For a general conversion to a specified arbitrary base you instead want just the number of digits sufficient to express the value, and for a negative value -V you want the digits of V, not a complement. – Cheers and hth. - Alf Feb 23 '15 at 03:37
  • Also, a difference with hex: C++ has a built-in operator `sizeof` to tell you the number of digits (used in the code above), but there is no such thing for an arbitrary base. – Cheers and hth. - Alf Feb 23 '15 at 04:37