Question

I have a template which takes a non-type template parameters of type unsigned long long. Instantiating this template quickly becomes confusing because it involves so many digits. In fact, ideally I would use a binary representation making it even worse: there are up to 64 0 and 1 characters. How can I create a visually recognizable argument. For example:

template <unsigned long long>
struct use_mask {
    // for this question it doesn't matter what the template actually does
};

int main() {
    use_mask<0b1001001010010010>    unreadable;    // OK, there are no binary literals
    use_mask<0b1001,0010,1001,0010> more_readable; // ... nor are there digit separators
}

Is there any way to approximate the latter notation, possibly with some stuff before and after the value?

Was it helpful?

Solution

Here is my take at answering my own question: Using a user-defined literal which is implemented as a constexpr together with a string literal allows, effectively, to specify a parser for the relevant value! There are two parts which are slightly ugly:

  1. The actual literal is enclosed by double quotes.
  2. There is a user-defined literal sitting at the end of said string.

Other than that, this approach actually allows specification of integer literals including digit separators. I'm currently not quite in the position to create a fancy version verifying that the separators sit in the correct location but that should be just a minor detail of proper programming. Below is a program implementing a corresponding user defined literal which allows uses like

use_mask<"0b0101,0101,0101,0101,0011,0101"_sep> um1;
use_mask<"0x0123,4567,89ab,cdef"_sep>           um2;

Of course, this is actually putting the literals to use, too. The actual literals are

"0b0101,0101,0101,0101,0011,0101"_sep
"0x0123,4567,89ab,cdef"_sep

... and they can be used entirely separately. The error messages throwing an exception aren't necessarily as pretty as I would like them to be. It was also pointed out that using a user-define literal to deal with the digit separators precludes the use of other user-defined literals.

#include <algorithm>
#include <iostream>
#include <stdexcept>

template <unsigned long long Value>
struct use_mask {
    static constexpr unsigned long long value = Value;
};

// ------------------------------------------------------------------------

constexpr bool is_digit(char c, unsigned base)
{
    return '0' <= c && c < '0' + int(base < 10u? base: 10u);
}

constexpr bool is_hexdigit(char c)
{
    return ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F');
}

constexpr unsigned long long hex_value(char c)
{
    return c - (('a' <= c && c <= 'f')? 'a': 'A') + 10;
}

// ------------------------------------------------------------------------

constexpr unsigned long long decode(unsigned long long value,
                                    unsigned base,
                                    char const* str, size_t n)
{
    return n == 0
        ? value
        : (str[0] == ','
           ? decode(value, base, str + 1, n - 1)
           : (is_digit(str[0], base)
              ? decode(value * base + str[0] - '0', base, str + 1, n - 1)
              : (base == 16u && is_hexdigit(str[0])
                 ? decode(value * base + hex_value(str[0]),
                          base, str + 1, n - 1)
                 : throw "ill-formed constant with digit separators"
                 )
              )
           );
}

constexpr unsigned long long operator"" _sep(char const* value,
                                             std::size_t n)
{
    return 2 < n && value[0] == '0'
        ? ((value[1] == 'b' || value[1] == 'B')
           ? decode(0ull, 2, value + 2, n - 2)
           : ((value[1] == 'x' || value[1] == 'X')
              ? decode(0ull, 16, value + 2, n - 2)
              : decode(0ull, 8, value + 1, n - 1)))
        : decode(0ull, 10, value, n);
}

int main()
{
    std::cout << use_mask<"0b1010,1010"_sep>::value << "\n";
    std::cout << use_mask<"02,52"_sep>::value << "\n";
    std::cout << use_mask<"1,70"_sep>::value << "\n";
    std::cout << use_mask<"0xA,A"_sep>::value << "\n";
#ifdef ERROR
    std::cout << use_mask<"0xx,A"_sep>::value << "\n";
#endif

    std::cout << use_mask<"0b0101,0101,0101,0101,0011,0101"_sep>::value
              << '\n';
    std::cout << use_mask<"0x0123,4567,89ab,cdef"_sep>::value << '\n';
}

OTHER TIPS

From C++14 onwards, this is valid:

use_mask<0b1001'0010'1001'0010> its_just_what_i_wanted;

Not perfect since it requires a comma between each binary digit, but allows for any amount of white space and for any const expr for any binary digit - so for example 0xFFFF FFFF FFFF FFFF whould be:

use_mask<bit64<
 1,1,1,1,  1,1,1,1,  1,1,1,1,  1,1,1,1,
 1,1,1,1,  1,1,1,1,  1,1,1,1,  1,1,1,1,
 1,1,1,1,  1,1,1,1,  1,1,1,1,  1,1,1,1,
 1,1,1,1,  1,1,1,1,  1,1,1,1,  1,1,1,1
>::VAL>

code:

template <
    bool b3F,bool b3E,bool b3D,bool b3C,bool b3B,bool b3A,bool b39,bool b38,
    bool b37,bool b36,bool b35,bool b34,bool b33,bool b32,bool b31,bool b30,
    bool b2F,bool b2E,bool b2D,bool b2C,bool b2B,bool b2A,bool b29,bool b28,
    bool b27,bool b26,bool b25,bool b24,bool b23,bool b22,bool b21,bool b20,
    bool b1F,bool b1E,bool b1D,bool b1C,bool b1B,bool b1A,bool b19,bool b18,
    bool b17,bool b16,bool b15,bool b14,bool b13,bool b12,bool b11,bool b10,
    bool b0F,bool b0E,bool b0D,bool b0C,bool b0B,bool b0A,bool b09,bool b08,
    bool b07,bool b06,bool b05,bool b04,bool b03,bool b02,bool b01,bool b00
>
class bit64
{
public:
    static const unsigned long long VAL=0UL
        |((unsigned long long)b00<< 0)|((unsigned long long)b01<< 1)|((unsigned long long)b02<< 2)|((unsigned long long)b03<< 3)
        |((unsigned long long)b04<< 4)|((unsigned long long)b05<< 5)|((unsigned long long)b06<< 6)|((unsigned long long)b07<< 7)
        |((unsigned long long)b08<< 8)|((unsigned long long)b09<< 9)|((unsigned long long)b0A<<10)|((unsigned long long)b0B<<11)
        |((unsigned long long)b0C<<12)|((unsigned long long)b0D<<13)|((unsigned long long)b0E<<14)|((unsigned long long)b0F<<15)
        |((unsigned long long)b10<<16)|((unsigned long long)b11<<17)|((unsigned long long)b12<<18)|((unsigned long long)b13<<19)
        |((unsigned long long)b14<<20)|((unsigned long long)b15<<21)|((unsigned long long)b16<<22)|((unsigned long long)b17<<23)
        |((unsigned long long)b18<<24)|((unsigned long long)b19<<25)|((unsigned long long)b1A<<26)|((unsigned long long)b1B<<27)
        |((unsigned long long)b1C<<28)|((unsigned long long)b1D<<29)|((unsigned long long)b1E<<30)|((unsigned long long)b2F<<31)
        |((unsigned long long)b20<<32)|((unsigned long long)b21<<33)|((unsigned long long)b22<<34)|((unsigned long long)b23<<35)
        |((unsigned long long)b24<<36)|((unsigned long long)b25<<37)|((unsigned long long)b26<<38)|((unsigned long long)b27<<39)
        |((unsigned long long)b28<<40)|((unsigned long long)b29<<41)|((unsigned long long)b2A<<42)|((unsigned long long)b2B<<43)
        |((unsigned long long)b2C<<44)|((unsigned long long)b2D<<45)|((unsigned long long)b2E<<46)|((unsigned long long)b3F<<47)
        |((unsigned long long)b30<<48)|((unsigned long long)b31<<49)|((unsigned long long)b32<<50)|((unsigned long long)b33<<51)
        |((unsigned long long)b34<<52)|((unsigned long long)b35<<53)|((unsigned long long)b36<<54)|((unsigned long long)b37<<55)
        |((unsigned long long)b38<<56)|((unsigned long long)b39<<57)|((unsigned long long)b3A<<58)|((unsigned long long)b3B<<59)
        |((unsigned long long)b3C<<60)|((unsigned long long)b3D<<61)|((unsigned long long)b3E<<62)|((unsigned long long)b3F<<63)
        ;
};

This could also be a constexpr for compilers that support it

An alternative is to use Macro:

#define ULongLongHex(p1, p2, p3, p4) (0x ## p1 ## p2 ## p3 ## p4)

ULongLongHex(0123,4567,89ab,cdef)

but no check. :(

I don't have a C++ compiler here to experiment, but you may try an approach along these lines (using the templated form of UDLs):

template<char... chars>
constexpr unsigned long long operator"" _bin();

template<char... chars>
constexpr unsigned long long operator"" _bin<'0', chars...>() {
  // return correctly shifted calculated value.
}

template<char... chars>
constexpr unsigned long long operator"" _bin<'1', chars...>() {
  // return correctly shifted calculated value.
}

template<char... chars>
constexpr unsigned long long operator"" _bin<'_', chars...>() {
  // simply forward to rest
}

Sorry for not being specific enough, I just don't have the tools here, but I've seen UDL parsing done this way.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top