Skip to content

[SUGGESTION] Literal Templates #316

Closed
@msadeqhe

Description

@msadeqhe

Preface

Both variables and literals have types in C++1, so we may explicitly specify the type of variables, and the type of literals with suffixes. For example:

// Here we explicitly specify the type of a variable.
unsigned long long x = 2;

// Here we explicitly specify the type of a literal.
auto y = 2ull;

But the name that we specify for the type of a variable is different from the name that we specify for the type of a literal. For example the type of a literal with suffix ull is unsigned long long integer. What if we specify the type of both variables and literals with the same name?

I should mention that literals are somehow unified with variables in C++2. For example of a function call:

x0 := /*smth*/.f();

// /*smth*/ may be a literal.
// Because of UFCS, f() calls a free function.
x1 := 2.f();

// /*smth*/ may be a variable.
// Because of UFCS, f() calls a free function or a member function.
x2 := x.f();

The same thing can be done for specifying the type of both variables and literals in C++2. if we specify the type of literals with a template argument which is named Literal Template:

x0 := /*smth*/<t>;

// /*smth*/ may be a literal.
// Because of UTVS, it's a literal template.
x1 := 2<t>;

// /*smth*/ may be a variable.
// Because of UTVS, it's a variable template.
x2 := x<t>;

UTVS means Uniform Typed Value Syntax. I will explain about it under "Why do I suggest this change?".

Suggestion Detail

C++1 already have the following templates:

  • Class Templates, e.g. Type<float>
  • Function Templates, e.g. function<float>()
  • Variable Templates (since C++14), e.g. variable<float>

I suggest to also have Literal Templates, e.g. 2.0<float>.

Literal templates are similar to variable templates, except they only have one template parameter which only accepts one of the predefined types for the literal. Predifined types for each literal are listed below:

// T can be only one of the following types:
// i8, ..., i64, u8, ..., u64, int, size, long, ulong, ...
// or any other integer type.
x1 := 2<T>;
y1 := 2<u8>;

// T can be only one of the following types:
// f16, ..., f128, float, double, ...
// or any other floating-point type.
x2 := 2.0<T>;
y2 := 2.0<f16>;

// T can be only one of the following types:
// c8, ..., c32, char, ...
// or any other character type.
x3 := 'x'<T>;
y3 := 'x'<c16>;

// T can be only one of the following types:
// c8, ..., c32, char, ...
// or any other character type.
x4 := "text"<T>;
y4 := "text"<c8>;

I need to explain as suggested in this issue:

  • f16, ..., f128 are type aliases to corresponding floatN_t types.
  • c8, ..., c32 are type aliases to corresponding charN_t types.

The template parameter of string literals are for their underlying character type, therefore if the template argument is c8 then the string literal is UTF-8.

// := u8"This text is UTF-8.";
x0 := "This text is UTF-8."<c8>;

Type Aliases are good enough to make literal templates convenient:

ull: type == ulonglong;

// OK. ull is an integer type.
x0 := 2<ull>;

fll: type == longdouble;

// ERROR! fll is not an integer type.
/**
  x1 := 2<fll>;
 */

2<fll> is not valid, because 2 only accepts a template argument of integer types.

Although it's technically possible to have user-defined literal templates, but I don't recommend it, because it may be misused.

Edit 6: User-defined Literal Templates will complement the concept of Literal Templates (see this comment for explanation and the cause of it, or continue reading the following comments). For example:

operator""kilo: <T>(value: T) -> T
requires std::is_integral_v<T> || std::is_floating_point_v<T>
= value * 1'000;

main: () = {
    sum: double = 1.5kilo + 2<ull>kilo;
}

In above example if {N} is an integer or a floting-point literal, then {N}kilo and {N}<T>kilo works.

Why do I suggest this change?

Because this change makes C++2 to have a more general language feature, and to be simpler to learn in the following ways:

1. Unification of how we use templates

Consider how we write 2.func() and x.func() to call func() regardless of whether they are literal or variable. The same thing will be possible for 3.14<float> and PI<float> to get the value in type float (without any type conversion) regardless of whether they are literal or variable. Types, functions, variables and literals will be familiar when working with types:

x0: TypeName<int> = ();
x1 := function_name<int>();
x2 := variable_name<int>;
x3 := 10<int>;

2. UTVS in generic programming

Literal templates allow literal types (also known as literal suffix or prefix) to be a template parameter (e.g. 10<T>), therefore with both variable templates and literal templates, C++2 will have Uniform Typed Value Syntax (UTVS) in generic programming. It means when only the type and the value of something is important, it doesn't matter if it was from a variable or a literal in generic programming. For example:

X: <Value: VariableOrLiteral, T: type>
type = {
    member := Value<T>;
}

x1 := X<PI, f16>;
x2 := X<3.14, f16>;

Value can be either a variable or a literal, anyway the implementation of X will work.

I need to explain that we cannot write PI as T for variable template PI<T>, therefore as cannot be used for UTVS. Also Value<T> for literals is different from Value as T in generic programming, because Value as T tries to safely convert Value to type T, but Value<T> doesn't change the type of literals, it only carries type T with Value, e.g. 10<T> doesn't convert the integer literal 10 to a floting-point type, because its template parameter is constrained to only accept integer types.

For more explanation see this comment.

3. Reducing Concept Count

Literal templates eliminate the concept of prefix and suffix for all type of literals, and make them to look uniform as their behaviour are naturaly similar to variable templates.

// Here we use <f16> template argument instead of f16 suffix.
// := 3.14f16 + 1.0;
x1 := 3.14<f16> + 1.0;
y1 := PI<f16> + 1.0; // PI is a variable template.

// Here we use <c8> template argument instead of u8 prefix.
// := u8"text";
x2 := "text"<c8>;
y2 := "text"<c8>sv; // y2 is std::basic_string_view<c8>.

4. UDL Improvement

C++1 has mixed the concept of User-defined Literal suffixes (UDL suffixes) with literal types, therefore there is a limit that UDL suffixes cannot be used at the same time with literal types (except for character and string literals which their type are written as prefix instead of postfix):

// This calls operator""ms(long double) in C++2.
// This is equal to 2.0ms in C++1.
x1 := 2.0<longdouble>ms;

// This calls operator""ms(f16) in C++2.
// NO! This isn't possible in C++1.
x2 := 2.0<f16>ms;

// This calls operator""ms(double) in C++2.
// This calls operator""ms(long double) in C++1.
x3 := 2.0ms;

I should point again that User-defined Literal Templates will complement the concept of Literal Templates (see this comment for explanation and the cause of it, or continue reading the following comments).

5. Consistent Literal Types

Built-in literal suffixes are inconsistent for integer and floating-point literals when the constant of a literal exceeds the type (which is specified with the suffix), and user-defined literal suffixes can replace built-in literal suffixes and prefixes (see this comment for explanation and the cause of it, or continue reading the following comments).

TL;DR. Literal template consistency in a nutshell:

ull: type == ulonglong;
c8: type == char8_t;

a: ull = 2<ull>;
b: c8 = 'x'<c8>;
c: float = 2.0<float>;
d: std::basic_string_view<c8> = "text"<c8>sv;

In the following paragraphs I'll explain in detail:

Firstly, The name we use to specify the type of variables (e.g. ulong) is different from the name we use to specify the type of literals (e.g. ul). For example:

// Here we explicitly specify the type of a variable with ulong.
x0: ulong = 2;

// Here we explicitly specify the type of a literal with ul.
x1 := 2ul;

On the other hand with literal templates, we use the same name to specify the type of variables and literals:

// Here we explicitly specify the type of a variable with ulong.
x0: ulong = 2;

// Here we explicitly specify the type of a literal with ulong.
x1 := 2<ulong>;

Secondly, to specify the type of literals in C++1, integer and floating-point literals have suffixes, but character and string literals have prefixes. For example:

// Suffix f specifies a literal with float type.
x1 := 2.0f;

// Prefix u8 specifies a literal with char8_t type.
x2 := u8'x';

On the other hand with literal templates, all of them use the existing concept of templates to achieve the same thing:

// Template argument float specifies a literal with float type.
x1 := 2.0<float>;

// Template argument c8 specifies a literal with c8 type.
x2 := 'x'<c8>;

6. All Possible Types

suffixes in C++1 doesn't support all possible types, e.g. int64_t. Fixed-width integer types are not supported with integer suffixes in C++1 as described in this question on Stack Overflow, but with literal templates all integer types are supported:

// It's not possible in C++1.
x0 := 2<i64>;

Even type aliases can be used:

il: type == long;

// OK. il is an integer type.
x0 := 2<il>;

x1 := 2<long>;

2<il> and 2<long> are excatly the same.

7. Unlocking More Versatile Syntax

By eliminating prefixes from character and string literals (also suggested in this issue), C++2 can additionally use more versatile syntax on desire, such as function'...' or function"...". For example see Tagged Templates in JavaScript.

Literal template syntax is not verbose, and it's readable.

It's not verbose with the help of type aliases, because only two characters < and > are added to the syntax:

ill: type == longlong;

// x: longlong = -10;
x: ill = -10;

// y := -10ll;
y := -10<ill>;

x: ill = -10; is less verbose than x: longlong = -10;.

Also -10ll may be misread as -1011, but -10<ill> is more expressive and easier to read.

Edits

  1. I've added another case under "Why do I suggest this change?".
  2. I've explained why Value<T> is different from Value as T in generic programming.
  3. I've explained why literal template syntax isn't verbose.
  4. I've arranged "Why do I suggest this change?".
  5. I've fixed grammar and spelling.
  6. I've corrected a wrong statement about User-defined Literal Templates (see this comment).
  7. I've added links to comments for more explanations.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions