15

On cppreference there is a mentioning that one can have templated user-literal operators, with some restrictions:

If the literal operator is a template, it must have an empty parameter list and can have only one template parameter, which must be a non-type template parameter pack with element type char, such as

template <char...> double operator "" _x();

So I wrote one like in the code below:

template <char...> 
double operator "" _x()
{
    return .42;
}

int main()
{
    10_x; // empty template list, how to specify non-empty template parameters?
}

Question:

  1. The code works, but how can I use the operator with some non-empty template parameters? 10_x<'a'>; or 10_<'a'>x; does not compile.
  2. Do you have any example of real-world usage of such templated operators?
vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • 1
    I've never seen that before, but it looks like the idea is to treat the characters before the prefix as `char`s at compile-time, rather than treating it as a numeric literal and passing it to a run-time function. – Kyle Strand Sep 30 '16 at 16:22
  • @KyleStrand But I'm still not sure how you use it (i.e., compile it with no errors). – vsoftco Sep 30 '16 at 16:25
  • Obviously I'm not either, since I've never seen it before! But I do have a working example I've just written, so I'll post that. – Kyle Strand Sep 30 '16 at 16:28
  • @KyleStrand Ohh I meant compiling successfully (not why one would use it). – vsoftco Sep 30 '16 at 16:29

3 Answers3

13
10_x; // empty template list, how to specify non-empty template parameters?

That isn't quite right. The template parameter list isn't empty. When you write:

template <char... Cs> 
??? operator "" _x()

The Cs get populated from the stuff on the left-hand side of the literal. That is, when you write:

10_x

that calls:

operator ""_x<'1', '0'>();

One simple example would be to build a compile time, overflow-safe binary literal such that:

template <uint64_t V>
constexpr uint64_t make_binary() {
    return V;
}

template <uint64_t V, char C, char... Cs>
constexpr uint64_t make_binary() {
    static_assert(C == '0' || C == '1', "invalid binary");

    return make_binary<2*V + C - '0', Cs...>();
}

template <char... Cs> 
uint64_t operator "" _b()
{
    static_assert(sizeof...(Cs) <= 64, "overflow");

    return make_binary<0, Cs...>();
}

uint64_t a = 101_b; // OK: a == 5
uint64_t b = 102_b; // error: invalid
uint64_t c = 11111111110000000000111111111100000000001111111111000000000011111111110000000000_b; // error: overflow
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Ohhh I see, yes, makes perfect sense, as the operator itself takes no arguments... But it seems there's absolutely no way to access those `char`s. – vsoftco Sep 30 '16 at 16:32
  • 2
    @vsoftco What do you mean? They're just like any other template non-type parameter. – Barry Sep 30 '16 at 16:34
  • I mean they don't have a name, so I cannot pull them out directly inside the operator. But @Kyle's example clarifies it a bit. Ok, now I see more scenarios in the comments, I realized you can name them. – vsoftco Sep 30 '16 at 16:36
  • 1
    @vsoftco You could just name them. – Barry Sep 30 '16 at 16:40
  • @vsoftco Also didn't realize that I failed to actually copy in the make binary example... – Barry Sep 30 '16 at 16:45
  • 2
    The nice thing about those is that with the right `constexpr` functions it allows you to diagnose overflow at compile time. – T.C. Sep 30 '16 at 17:14
7

Your template parameters are already specified--they're the source-code characters comprising your literal value! So for 10_x, you're actually calling:

template<> double operator "" _x<'1', '0'>();

Here's a working example. It compiles without error, and none of the assertions are triggered.

#include <cassert>

enum class MyEnum
{
  ONE,
  TWO,
  THREE
};

template<char...> MyEnum operator "" _e();

template<> MyEnum operator "" _e<'1'>()
{
  return MyEnum::ONE;
}
template<> MyEnum operator "" _e<'2'>()
{
  return MyEnum::TWO;
}
template<> MyEnum operator "" _e<'3'>()
{
  return MyEnum::THREE;
}

int main()
{
  assert(1_e == MyEnum::ONE);
  assert(2_e == MyEnum::TWO);
  assert(3_e == MyEnum::THREE);
}
Kyle Strand
  • 15,941
  • 8
  • 72
  • 167
  • That's a nice example! So the only way to *access* the `char`s are via specializations. – vsoftco Sep 30 '16 at 16:33
  • 3
    No, you could also do for example `template const char* operator "" _cs() { static const char arr[] = {Cs..., '\0'}; return arr; }` – aschepler Sep 30 '16 at 16:35
  • 1
    @aschepler It took me a while to realize why you can re-initialize the `static`: because you have different operators for each template parameter. – vsoftco Sep 30 '16 at 16:52
  • 1
    @vsoftco Right, so it's not truly re-initialization--it's separate initialization of separate `static` values. I'm sure there are other clever tricks as well... – Kyle Strand Sep 30 '16 at 17:01
2

You can elaborate the parameters pack somehow (as mentioned by others) or access them as a compile-time string if you prefer:

template<int N>
constexpr double f(const char(&str)[N]) { return .42; }

template <char... C> 
constexpr double operator "" _x()
{
     return f({C...});
}

Do you have any example of real-world usage of such templated operators?

You can use the above mentioned technique to deal with compile-time string-to-num converter and have something like 10_x instead of f("10") or whatever.

skypjack
  • 49,335
  • 19
  • 95
  • 187