Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SUGGESTION] Down with typename! #531

Closed
JohelEGP opened this issue Jul 1, 2023 · 10 comments
Closed

[SUGGESTION] Down with typename! #531

JohelEGP opened this issue Jul 1, 2023 · 10 comments

Comments

@JohelEGP
Copy link
Contributor

JohelEGP commented Jul 1, 2023

First expressed at #529 (comment) (which occurred to me when working on that PR).

Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code? 👌 (zero).

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

If yes, please be specific about what current good guidance this helps make the default, and/or what guidelines we would no longer need to teach/learn or that would be simplified and how,

  • T.42 would be simplified by reducing the list of cases to which the enforcement can possibly apply.

with an example or two (especially a link to a real "Effective C++" or "C++ Core Guidelines" guideline or two).

Assuming that Cpp2 supports typename for disambiguation, just like Cpp1.

-x: typename T::type = ();
+x: T::type = ();
-x is typename T::type
+x is T::type
  • Please limit suggestions to quantifiable improvements to C++ simplicity, safety, or toolability. Quantifiable means that there is some kind of measurable data that helps motivate the change and measure success. Two of the big ones that will get my attention are eliminating vulnerabilities and eliminating guidance, so use those below please.

For a measure,
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2150r0.html#5.0
proposes to remove 254 uses of typename from the C++ standard library wording
(extracted from the linked section's HTML by counting <del>typename</del>s).

There's 765 occurrences of 'typename ' in the C++ standard library,
not all disambiguating typenames
(extracted from the library sources).
That's a 33% reduction.

Describe alternatives you've considered.
None.
There's nothing better than saying nothing at all when there's no room for another interpretation.

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 1, 2023

Note that because the Cpp2 grammar is unambiguous
we can have optional typename where Cpp1 can't.

For example, f: <T> (x: T::foo) = { } is a template function.
The equivalent Cpp1 means something different if typename is omitted:

template<typename T> T x(typename T::foo); // function template
template<typename T> T x(T::foo);          // variable template

-- Extract from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2150r0.html#3.1.1.

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 1, 2023

For a measure,
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2150r0.html#5.0
proposes to remove 254 uses of typename from the C++ standard library wording
(extracted from the linked section's HTML by counting <del>typename</del>s).

There's 765 occurrences of 'typename ' in the C++ standard library,
not all disambiguating typenames
(extracted from the library sources).
That's a 33% reduction.

In Cpp2, the only grammar that could be an expression or type-id is a template-argument.
That's the only grammar production where disambiguating typename can't be made optional.
So if the C++ standard library were written in Cpp2, the reduction would be over 80%.

Now, the C++ standard library isn't such a good example because it's not actual C++ code.
It's written on prose, reducing the expressions that need disambiguating typename.

@hsutter
Copy link
Owner

hsutter commented Jul 1, 2023

First: I love the title just as much now as when I first saw Nina and Daveed use it :)

Can you elaborate on what the proposal is though? Cpp2 currently doesn't use typename, and if I discover any places where it would be needed my first thought would be to fix the grammar so it's unambiguous.

Assuming that Cpp2 supports typename for disambiguation, just like Cpp1.

-x: typename T::type = ();
+x: T::type = ();

The first line should not be allowed now, and the second is unambiguously a variable.

-x is typename T::type
+x is T::type

is is interesting:

  1. Dependent types: That x is T::type should work but doesn't yet, thanks for the bug report. It ought to emit typename for a dependent type... in this case it should generate cpp2::is<typename T::type>(x). But currently it emits cpp2::is<T::type>(x) which fails the Cpp1 compiler. I'll need to fix that.

  2. Dependent (static) values:

a) Currently x is (T::value) works, and requires ( ) which makes it unambiguous.

b) Note that to test this you have to write the type in Cpp1, because I haven't yet provided a way to author static values in Cpp2. I probably will allow them, but require them to be constinit or constexpr.

Here's a working test program:

struct Test {
    using type = int;
    static inline int value = 42;
};

dependent_is: <T> () = {
    x := 42;
    std::cout
        // (x is T::type) << "\n" // doesn't work yet, need to inject `typename`
        << (x is (T::value)) << "\n";
}

main: () = {
    x := 42;
    std::cout << std::boolalpha
        << (x is int) << "\n" // "true"
        << (x is 42) << "\n"; // "true"
    dependent_is<Test>();     // "true"
}

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 1, 2023

Can you elaborate on what the proposal is though?

For this to work:

t: type = {
  type: type == i32;
}
f: <T, V: T::type> (x: T::type) -> T::type = :T::type = x;
main: () -> int = f<t, 0>(0);

All those T::type are type-only contexts,
and should be emitted with typename in front (or omitted if it's already optional).

I'm actually working on a solution.
It currently generates:

#define CPP2_USE_MODULES         Yes

//=== Cpp2 type declarations ====================================================


#include "cpp2util.h"

#line 1 "pure2-optional-typename.cpp2"
class t;
  

//=== Cpp2 type definitions and function declarations ===========================

#line 1 "pure2-optional-typename.cpp2"
class t {
  public: using type = cpp2::i32;

  public: t() = default;
  public: t(t const&) = delete; /* No 'that' constructor, suppress copy */
  public: auto operator=(t const&) -> void = delete;
#line 3 "pure2-optional-typename.cpp2"
};
template<typename T, typename T::type V> [[nodiscard]] auto f(cpp2::in<typename T::type> x) -> typename T::type;
[[nodiscard]] auto main() -> int;


//=== Cpp2 function definitions =================================================


#line 4 "pure2-optional-typename.cpp2"
template<typename T, typename T::type V> [[nodiscard]] auto f(cpp2::in<typename T::type> x) -> typename T::type { return typename T::type{x};  }
[[nodiscard]] auto main() -> int { return f<t,0>(0);  }

@JohelEGP

This comment was marked as resolved.

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 4, 2023

and if I discover any places where it would be needed my first thought would be to fix the grammar so it's unambiguous.

There is a grammar where it's needed: template-argument.
In identity<std::pointer_traits<ptr>::rebind<ptr>>,
std::pointer_traits<ptr>::rebind<ptr> parses as an expression,
so typename would be required to make it a type.
See commit 8ae90d7.

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 5, 2023

Would you be solving the ambiguity in template-argument the same as with is-as-expression?
I.e., making type-id be the first production of template-argument.

I think that would make sense, and be consistent.

  • std::array<i32, 42> would continue working.
  • Most template arguments are types,
    and will probably continue to be even with increasing NTTP support.
  • Adding disambiguating parenthesis around an expression isn't too noisy.

If done, there might be one difference between template-argument and is-as-expression.

  • e is e and e is (std::vector) fail at Cpp1 time if e is an expression
    (the first one should be e is (e), and the second one e is std::vector).
  • t<e> would parse e as a type-id.
    But if it's lowered as-is, it might not fail at Cpp1 time if e is actually an expression.
    That's because the Cpp1 syntax t<x> is the same whether x is a type or expression.
    So to get the same benefit of failing at Cpp1 time due to a kind-of-entity mismatch,
    it'd be necessary to lower t<e> as t<cpp2::t<e>>,
    where cpp2::t is defined to ensure e is a type:
    template<class T> using t = T;

That would also
resolve #502,
resolve #456, and
close #521.

@hsutter
Copy link
Owner

hsutter commented Jul 5, 2023

Would you be solving the ambiguity in template-argument the same as with is-as-expression?

Ack: You beat me to it -- I've been out of computer range but was going to reply saying I expected it would be the same as x is type and x is (value). The parens aren't needed for things like literals, of course.

I hope to finally be back in reliable computer range again on Friday...

@JohelEGP
Copy link
Contributor Author

JohelEGP commented Jul 31, 2023

If done, there might be one difference between template-argument and is-as-expression.

  • e is e and e is (std::vector) fail at Cpp1 time if e is an expression
    (the first one should be e is (e), and the second one e is std::vector).
  • t<e> would parse e as a type-id.
    But if it's lowered as-is, it might not fail at Cpp1 time if e is actually an expression.
    That's because the Cpp1 syntax t<x> is the same whether x is a type or expression.
    So to get the same benefit of failing at Cpp1 time due to a kind-of-entity mismatch,
    it'd be necessary to lower t<e> as t<cpp2::t<e>>,
    where cpp2::t is defined to ensure e is a type:
    template<class T> using t = T;

-- Extract from #531 (comment).

The same is true for an expression template argument that is lowered without the parentheses.
0 is (0) lowers to cpp2::is(0, (0)).
So if t<(i32)> lowers to t<(cpp2::i32)>, that's a Cpp1-time error 👍.
If it lowered to t<i32>, that'd be a semantic change from the parsed Cpp2 (from Cpp2 expression to Cpp1 type) 👎.

However, that means that changing template-argument to consider type-id before expression
breaks current uses of decltype template arguments, which happens to work today.
That's because t<decltype(x)> parses decltype(x) as an expression, lowers as-is, but is a Cpp1 type.
So if decltype isn't made a Cpp2 type-id before the change above, we lose the ability to use decltype in Cpp2.

@JohelEGP
Copy link
Contributor Author

Would you be solving the ambiguity in template-argument the same as with is-as-expression?

Ack: You beat me to it -- I've been out of computer range but was going to reply saying I expected it would be the same as x is type and x is (value). The parens aren't needed for things like literals, of course.

I hope to finally be back in reliable computer range again on Friday...

Note that although #533 marks this PR as to-be-fixed by merging it, it doesn't implement this idea.
So #533 doesn't provide a way to pass type template arguments.

Repository owner locked and limited conversation to collaborators Aug 30, 2023
@hsutter hsutter converted this issue into discussion #628 Aug 30, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

2 participants