Skip to content

[SUGGESTION] Down with typename!聽#531

Closed

Description

@JohelEGP

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.

Activity

  1. JohelEGP commented on Jul 1, 2023

    @JohelEGP
    ContributorAuthor

    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.

  2. JohelEGP commented on Jul 1, 2023

    @JohelEGP
    ContributorAuthor

    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.

  3. hsutter commented on Jul 1, 2023

    @hsutter
    Owner

    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"
    }
    
  4. JohelEGP commented on Jul 1, 2023

    @JohelEGP
    ContributorAuthor

    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);  }
    
  5. JohelEGP commented on Jul 1, 2023

    @JohelEGP
    Author
  6. JohelEGP commented on Jul 4, 2023

    @JohelEGP
    ContributorAuthor

    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.

  7. JohelEGP commented on Jul 5, 2023

    @JohelEGP
    ContributorAuthor

    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.

  8. hsutter commented on Jul 5, 2023

    @hsutter
    Owner

    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...

  9. JohelEGP commented on Jul 31, 2023

    @JohelEGP
    ContributorAuthor

    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.

  10. JohelEGP commented on Aug 27, 2023

    @JohelEGP
    ContributorAuthor

    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.

  11. Repository owner locked and limited conversation to collaborators on Aug 30, 2023
  12. converted this issue into a discussion #628 on Aug 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions