celticminstrel
celticminstrel

Reputation: 1679

Any way to make parameterized user defined literals?

A little while ago I had an idea about "parameterized" user-defined literals and was wondering if there is any way to do this in the current C++ standard.

Basically, the idea is to have a user-defined literal whose behaviour can be tweaked according to some parameters. As a simple example, I chose a "fixed-point" literal which turns a floating-point number into an integer; the parameter is the precision in terms of the number of decimal places.

This is just an exercise for now, since I'm not sure how or if this would be useful in a real application.

My first idea went something like this:

namespace fp_impl {
    constexpr int floor(long double n) {
        return n;
    }

    constexpr int pow10(int exp) {
        return exp == 0 ? 1 : 10 * pow10(exp - 1);
    }

    template<int i>
    constexpr int fixed_point(long double n) {
        return floor(n * pow10(i));
    }

    namespace fp2 {
        constexpr int operator"" _fp (long double n) {
            return fixed_point<2>(n);
        }
    }

    namespace fp4 {
        constexpr int operator"" _fp (long double n) {
            return fixed_point<4>(n);
        }
    }
}

template<int prec> struct fp;
template<> struct fp<2> {
    namespace lit = fp2;
};
template<> struct fp<4> {
    namespace lit = fp4;
};

int main() {
    {
        using namespace fp<2>::lit;
        std::cout << 5.421_fp << std::endl; // should output 542
    }
    {
        using namespace fp<4>::lit;
        std::cout << 5.421_fp << std::endl; // should output 54210
    }
}

However, it doesn't compile because namespace aliases are not permitted at class scope. (It also has a problem with requiring you t manually define every version of operator"" _fp.) So I decided to try something with macros:

namespace fp {
    namespace detail {
        constexpr int floor(long double n) {
            return n;
        }

        constexpr int pow10(int exp) {
            return exp == 0 ? 1 : 10 * pow10(exp - 1);
        }

        template<int i>
        constexpr int fixed_point(long double n) {
            return floor(n * pow10(i));
        }
    }
}

#define SPEC(i) \
    namespace fp { \
        namespace precision##i { \
            constexpr int operator"" _fp(long double n) { \
                return fp::detail::fixed_point<i>(n); \
            } \
        } \
    }
SPEC(2); SPEC(4);
#undef SPEC
#define fp_precision(i) namespace fp::precision##i

int main() {
    {
        using fp_precision(2);
        std::cout << 5.421_fp << std::endl;
    }
    {
        using fp_precision(4);
        std::cout << 5.421_fp << std::endl;
    }
}

This works, though it still has the requirement of using the SPEC() macro for every precision you ever want to use. Of course, some preprocessor trickery could be used to do this for every value from, say, 0 to 100, but I'm wondering if there could be anything more like a template solution, where each one is instantiated as it is needed. I had a vague idea of using an operator"" declared as a friend function in a template class, though I suspect that won't work either.

As a note, I did try template<int i> constexpr int operator"" _fp(long double n), but it seems this is not an allowed declaration of a literal operator.

Upvotes: 4

Views: 1335

Answers (3)

Paul Preney
Paul Preney

Reputation: 1293

One does not need macros to solve the problem. Since the problem concerns processing literals numbers (e.g., integers or floating-point formatted numbers), one can use the template definition of the literal operator and template metaprogramming to do the job completely at compile-time.

To do your fixed-point literal conversions, you could use the integer literal operator with unsigned long long, e.g.,

some_type operator "" _fp(unsigned long long num)
{
  // code
}

(or with long double with possible loss of precision) but this causes everything to happen at run-time.

C++11 in section 2.14.8 (User-defined Lierals [lex.ext]) within paragraphs 3 and 4 define literal operator variations including a template version for integer and floating-point literals! Unfortunately, paragraphs 5 and 6 do not define a template version for string and character literals. This means this technique will only work with integer and floating-point literals.

From C++11 section 2.14.8 the above _fp literal operator can therefore be written instead as:

template <char... Digits>
constexpr some_type operator "" _fp()
{
  return process_fp<2, Digits...>::to_some_type();
}

e.g., where the 2 is a value from the int i template parameter from the OP and some_type is whatever the return type needs to be. Notice that template parameter is a char --not an int or some other number. Also notice that the literal operator has no arguments. Thus code like Digit - '0' is needed to get the numeric value to an integer value for that character. Moreover, Digits... will be processed in a left-to-right order.

Now one can use template metaprogramming with process_fp whose forward declaration would look like:

template <int i, char... Digits>
struct process_fp;

and would have a static constexpr method called to_some_type() to compute and return the desired, compile-time result.

One might also want a meaningful, simple example of this is done. Last year I wrote code (link below) that when used like this:

int main()
{
  using namespace std;

  const unsigned long long bits =
    11011110101011011011111011101111_binary;
  cout << "The number is: " << hex << bits << endl;
}

would convert the binary number 11011110101011011011111011101111 into an unsigned long long at compile-time and store it into bits. Full code and explanation of such using the template metaprogramming technique referred to above is provided in my blog entry titled, Using The C++ Literal Operator.

Upvotes: 0

Johannes Schaub - litb
Johannes Schaub - litb

Reputation: 506965

You can return a class type that has operator()(int) overloaded from your literal operator. Then you could write

5.421_fp(2);

Upvotes: 8

Potatoswatter
Potatoswatter

Reputation: 137810

A user-defined literal function takes as its sole argument the literal itself. You can use state outside the function, for example with a global or thread-local variable, but that's not very clean.

If the argument will always be compile-time constant, and it's part of the number, pass it through the literal itself. That requires writing an operator "" _ ( char const *, std::size_t ) overload or template< char ... > operator "" _ () template and parsing the number completely by yourself.

You will have to work such a parameter into the existing floating-point grammar, though. Although C++ defines a very open-ended preprocessing-number construct, a user-defined literal must be formed from a valid token with a ud-suffix identifier appended.

You might consider using strings instead of numbers, but then the template option goes away.

Upvotes: 2

Related Questions