Skip to content

[SUGGESTION] Literal suffixes are constructors. #455

@msadeqhe

Description

@msadeqhe

1. Preface

Literal suffixes are syntactic sugars to constructors. Considering Unified Function Call Syntax for Member Functions and Non-member Functions, this suggestion is somehow something similar for Literal Suffixes and Constructors.

Briefly, I suggest to support this:

a: = 1'000ul * 5.0float;
b: = "text"s8.size();
c: = (2.5litre)water.weight();
d: = ((1my::box)apple + (2my::box)orange).weight();

name: = ask_player_name();
age: = ask_player_age();
p: = (name, age)my::player;
p.buy(()m4gun, 30bullet);

I have to explain:

  • ul and s8 are type aliases in 1'000ul and "text"s8.size() respectively.
  • (2.5litre)water creates a variable from litres constructor, and passes it to waters constructor.
  • (name, age)my::player creates a variable from type player with (name, age) constructor.
  • ()m4gun creates a variable from type m4gun with default constructor.

2. Suggestion Detail

Currently, Cpp2 doesn't have a special syntax to directly call constructors. I suggest to directly call Constructors of a type in the form of Literal Suffixes. Let's name it Direct Object Construction Syntax or Constructor Call Syntax.

So ...TYPE will be a syntactic sugar to (: TYPE = ...) in Cpp2. For example:

//: = (: something = 2);
x0: = 2something;

//: = (: taip = ("text", 0));
x1: = ("text", 0)taip;

It requires to remove all built-in literal prefixes and suffixes:

  • Remove l, ll, ul, ull suffixes from integer literals.
  • Remove f, l, f8, f16, f32, f64 suffixes from floating-point literals.
  • Remove u, U, u8, u16, u32 prefixes from both character and string literals.
    • Also removing R and $ prefixes from string literals as described in this issue, will make all literals to be consistent.

Constructors and UDLs (user-defined language literals) are two ways in Cpp1 to create objects from literals:

// -- It calls the constructor of `something`,
// -- therefore it needs parenthesis in Cpp1, otherwise it would be `something1` which is an identifier!
something(1)
// -- It doesn't call the constructor of `something`, because it's UDL.
1something

The following expression in Cpp2 satisfies the purpose of both two lines above:

// -- It calls the constructor of `something`,
// -- but it doesn't need parenthesis in Cpp2, because it looks like UDLs with somehow stronger behaviour.
1something

These are some notes to consider:

  1. Type aliases make this suggestion, easier, simpler, readable and a replacement for UDLs. For example:
ul: type == ulong;
s8: type == std::u8string;

a: = 1'000ul;  // -- It's equal to 1'000ul in Cpp1.
b: = "text"s8; // -- It's equal to u8"text" in Cpp1.
  1. They can be within namespaces, because they are types:
// -- `box` is a type within namespace `my`.
x: = 2my::box;
  1. Multiple constructors (aka UDLs) can be applied to literals. For example:
// -- `litre` and `water` are types.
c: = (2.5litre)water;
  1. They can be applied to multiple literals, as they are arguments to call the constructor. For example:
// -- `player` is the type within namespace `my`.
p: = (name, age)my::player;
  1. They can call default constructor with (). For example:
// -- `m4gun` is the type.
x: = ()m4gun;
  1. They can be used with other operators. For example:
a: = 1'000ul * 5.0float;
b: = "text"s8.size();
c: = (2.5litre)water.weight();
d: = ((1my::box)apple + (2my::box)orange).weight();

name: = ask_player_name();
age: = ask_player_age();
p: = (name, age)my::player;
p.buy(()m4gun, 30bullet);

If Cpp2 would have array literals as described in this issue, a similar syntax would be available to call the constructor for them. For example in a consistent way with other literals, parentheses around [...] aren't necessary:

//: = (: std::vector<int> = [1, 2, 3]);
x0: = [1, 2, 3]std::vector<int>;

dict: <T> type = std::vector<std::pair<std::string, T>>;
//: = (: dict<int> = [("a", 1), ("b", 2)]);
y0: = [("a", 1), ("b", 2)]dict<int>;

Consider how ...TYPE is expressive and more readable than (: TYPE = ...), that's the reason why Cpp1 has UDLs. For example:

//: = (: point<int> = (1, 2)) * (: std::vector<int> = [1, 2, 3]);
ab: = (1, 2)point<int> * [1, 2, 3]std::vector<int>;

//: =(: box = ((: apple = 10) + (: orange = 20)));
mn: = (10apple + 20orange)box;

//: = player.buy((: apple = (: kg = 1)), (: health = (: box = 2)));
uv: = player.buy(2gun, (2box)health);

It's possible to consume Cpp1 UDLs. For example:

// -- `ms` is Cpp1 UDL.
//: = (: my::clock = (operator""ms(: ulonglong = 10)));
ab: = ((10ulonglong)ms)my::clock;

Constructors can replace Cpp1 UDLs completely, but Cpp2 can still support to author UDLs (user-defined literal suffixes, e.g. operator""suffix). Probably the plan is to only consume UDLs as described in this comment from @hsutter.

3. Your Questions

Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?

No.

Will your feature suggestion automate or eliminate X% of current C++ guidance literature?

Yes, because this change makes Cpp2 to reduce the concept count with a general language feature. So it will be simpler to learn and understand which leads to smaller guidance literature.

  1. It unifies constructors with UDLs. They are semantically the same. Both of them create a new object.
    1. It's useful in generic programming.
    2. It reduces concept count.
      • Novice programmers don't need to learn a distinct concept about UDLs.
      • All types benefit from UDL like syntax. It's not needed to declare UDL for them.
      • It eliminates the need of understanding and learning built-in prefixes and suffixes for literals.
    3. The syntax of calling constructors will be expressive and readable.
  2. It distincts constructors from regular function calls. They are semantically different.
    • Constructors:
      • ...TYPE, parentheses are not necessary when ... is only one literal.
      • ()TYPE, it calls the default constructor
      • (args...)TYPE
    • Regular Function Calls:
      • FUNCTION(), it calls a function without arguments
      • FUNCTION(args...)
      • obj.FUNCTION()
      • obj.FUNCTION(args...)
  3. They can be chained together, whereas it's not possible with UDLs in Cpp1.
    • Only one UDL can be applied to a literal in Cpp1.
  4. Constructors already can be templated, but UDLs cannot be templated.
    • UDL templates are not supported in Cpp1.
  5. It removes built-in literal prefixes and suffixes. They are inconsistent and redundant.
    1. They are visually inconsistent.
      • Some of them are prefix.
      • Some of them are suffix.
    2. Their behaviours are inconsistent when the constant of literal exceeds the type as described in this comment.
  6. The name to construct a literal and to declare a variable will be consistently the same.
    • It's not needed to declare a new name for literal suffixes.
    • The name of types are like a suffix that will construct an object.
  7. They can be applied to literals with qualified name (if they are within namespaces) unlike UDLs which need using statement before they can be applied to literals.
    • That's why UDLs in Cpp1 have to be prefixed with _, thus they will be distinguished from UDLs which are declared in the Cpp1 standard library.

4. More Examples

By declaring type aliases to have familiar names:

ul: type == ulong;
ull: type == ulonglong;
s8: type == std::u8string;

x: ull = 2 + 2ul + 2ull;
y: = (0, 0)point + (0, 0)point;
call((0, 0)point, "text"s8.size());

m: = my::http::download("http://somewhere/somefile.ext"url.encode());
// -- `min` and `s` are Cpp1 UDLs from `std::chrono`.
n: = (1min + 10s)my::clock;

The process of object constructions will be simpler and readable:

p1: player = ("Sam"id, 1year);
p2: player = (112id, 2year);
p3: player = ((114)id, 1year + 4month);
p4: player = (("Alex", 110)id, 3year + 3month);

((p2, p3)team, (p1, p4)team)battle.start();

5. Considered Alternatives

This suggestion is a simpler and generalized alternative way to both this issue and this issue, with a different approach. This suggestion completely unifies literal suffixes with constructors instead of integrating them with templates.

Edits

  1. Haskell is a better language for syntax highlighting my Cpp2 examples! 😅

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions