HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

User defined literal for std::integral_constant

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
stduserliteralintegral_constantfordefined

Problem

I created a user defined literal _c to convert an "integer" literal into an std::integral_constant. Basically, the goal is to allow users to write std::integral_constant instances without the usual boilerplate. Here is the implementation:

#include 

template
constexpr auto pow_helper(T acc, T value, U times)
    -> T
{
    return (times > 1) ?
        pow_helper(acc*value, value, times-1) :
            acc;
}

// Compile-time pow function, only works with
// an unsigned integer exponent
template
constexpr auto pow(T value, U exponent)
    -> T
{
    return (exponent == 0) ? 1 :
        (exponent > 0) ? pow_helper(value, value, exponent) :
            1 / pow_helper(value, value, -exponent);
}

// Structure to parse an integer literal
template
struct parse
{
    static constexpr Integral value =
        parse::value * pow(10u, sizeof...(Digits))
        + parse::value;
};

// Specialization of parse to parse a single
// decimal digit
template
struct parse
{
    static_assert(C >= '0' && C 
constexpr auto operator"" _c()
    -> std::integral_constant::value>
{
    return {};
}


With this literal, wirting 42_c generates an instance of std::integral_constant. Here is a small working example:

int main()
{
    std::cout >::value, "");
}


To generate other integral constants, I plan to add the user defined literals _cl, _cll, _cu, _cul and _cull whoe implementation is exactly the same, only the resulting type differs.

Is there a way to improve this code and/or make it cleaner or more idiomatic? Have I missed some potential flaws?

Solution

Usage of "fat" templates

I would try to avoid using full-blown class templates when there's an elegant alternative solution with alias templates or constexpr functions. Note that I didn't measure this, so take it with a grain of salt: lightweight constexpr functions and alias template can be faster to instantiate. Using constexpr functions might reduce the number of instantiations to a minimum of 1 (the literal operator template) or two (the constexpr function template with a single template type parameter).

Algorithm

By changing the order of operations, you can get rid of the pow function (template) entirely: Pass the current result to the next step, then multiply and add. The shifting (in base 10) will be done on the fly.

Sketch:

constexpr int combine(int p)
{
    return p;
}

template
constexpr int combine(int val, int p0, TT... pp)
{
    return combine(val*10 + p0, pp...);
}

constexpr int parse(char C)
{
    return (C >= '0' && C 
constexpr auto operator"" _c()
-> std::integral_constant
{
    return {};
}


Literals in other bases

Your parse function currently rejects hexadecimal, octal, and in C++1y binary literals, as well as C++1y's digit separators. Supporting the bases digit conversion. IIRC, it's not guaranteed that the letters are contiguous in the basic execution character set (as opposed to the digits), so this could be rather painful w/o a switch or lookup-table. Also, upper/lower case.

Automatically growing literal type

Normal C++ literals automatically adjust their types according to their value. If they don't fit into an int, they'll try long, long long etc. Maybe one of the user-defined literals should mimic that behaviour for convenience.

Operators

Unfortunately, the StdLib doesn't provide operators for std::integral_constant. Therefore, -23_c will not be a std::integral_constant but rather an int (via the implicit conversion operator). I find that surprising.

Consider using a custom type, possibly derived from / convertible to std::integral_constant and provide (metaprogramming) operators for this type.

Side remark:

lit is much easier to implement:

template
using lit = std::integral_constant;

Code Snippets

constexpr int combine(int p)
{
    return p;
}

template<class... TT>
constexpr int combine(int val, int p0, TT... pp)
{
    return combine(val*10 + p0, pp...);
}

constexpr int parse(char C)
{
    return (C >= '0' && C <= '9')
           ? C - '0'
           : throw std::out_of_range("only decimal digits are allowed");
}

template<char... Digits>
constexpr auto operator"" _c()
-> std::integral_constant<int, combine(0, parse(Digits)...)>
{
    return {};
}
template<std::uintmax_t N>
using lit = std::integral_constant<std::uintmax_t, N>;

Context

StackExchange Code Review Q#50910, answer score: 9

Revisions (0)

No revisions yet.