Skip to content
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

[BUG] Add support for defining template specializations. #467

Open
leejy12 opened this issue May 23, 2023 · 11 comments · May be fixed by #598
Open

[BUG] Add support for defining template specializations. #467

leejy12 opened this issue May 23, 2023 · 11 comments · May be fixed by #598
Labels
bug Something isn't working

Comments

@leejy12
Copy link

leejy12 commented May 23, 2023

AFAIK, there's no way to define template specializations in cppfront. I found this out when trying to define a custom std::formatter.

Here's my expected syntax.

Foo: @struct type = {
    x: i32 = 0;
}

std::formatter<Foo>: <> @struct type = {
    parse: (this, inout ctx: std::format_parse_context) -> _ = {
        return ctx.end();
    }

    format: <FormatContext> (this, foo: Foo, inout ctx: FormatContext) -> _ = {
        return std::format_to(ctx.out(), "Foo({})", foo.x);
    }
}

As of now, cppfront doesn't attempt to compile the std::formatter<Foo> part.

https://godbolt.org/z/eKd8Y9aWz

@leejy12 leejy12 added the bug Something isn't working label May 23, 2023
@JohelEGP
Copy link
Contributor

JohelEGP commented May 23, 2023

std::formatter<Foo>: <> @struct type = { }

The diamonds come after the metafunctions.

Some more examples:

::std::formatter<Foo>: @struct <> type = { }

same1: <T, U> type == std::false_type;
same1<T, T>: <T> type == std::true_type;

same2: <T, U> type == std::false_type;
same2<T, U>: <T, U> type requires std::same_as<T, U> == std::true_type;

same_v1: <T, U> const bool = false;
same_v1<T, T>: <T> const bool = true;

same_v2: <T, U> const bool = false;
same_v2<T, U>: <T, U> const bool requires std::same_as<T, U> = true;

Another thing to consider is how a nested namespace definition will be supported
(see also #438 (comment)):

std::inline literals: namespace = { }

The current grammar has identifiers before a declaration's colon only:

name: (
  name: i32,
  in name: i32
) = { }
name: type = {
  public data: i32;
  operator=: (implicit in this) = { } // `this` doesn't permit `:` yet.
}

export name: () = { } pending #269.

Supporting the specialization of templates
introduces :: and arbitrary expressions before the :
(and tentatively the inline qualifier for namespaces).
Maybe not function expressions, see #385.

The expressions, unfortunately, complicate parsing.
https://en.cppreference.com/w/cpp/experimental/is_detected#Possible_implementation:

has_value_: <T, Void> type == std::false_type;
has_value_<T, std::void_t<decltype(T().value)>>: // Would need to parse arbitrary expressions before `:`.
  <T> type == std::true_type;

has_value: <T> type == has_value_<T, void>;

@leejy12
Copy link
Author

leejy12 commented May 25, 2023

@JohelEGP Unrelated, but how will this code be compiled?

same1: <T, U> type == std::false_type;
same1<T, T>: <T> type == std::true_type;

Because

template <typename T, typename U>
using same1 = std::false_type;

template <typename T>
using same1<T, T> = std::true_type;

is invalid Cpp1.

@JohelEGP
Copy link
Contributor

That was my mistake. It should have been this:

same1: <T, U> type = {
  this: std::false_type = ();
}
same1<T, T>: <T> type = {
  this: std::true_type = ();
}

same2: <T, U> type = {
  this: std::false_type = ();
}
same2<T, U>: <T, U> type requires std::same_as<T, U> = {
  this: std::true_type = ();
}

@MaxSagebaum
Copy link
Contributor

Just an idea: Directly specifying the specialized template arguments in the declaration name comes from the current cpp1 syntax. In cpp2 syntax this could be moved nearly anywhere else. So a few examples:

same1: <T, U> type = {
  this: std::false_type = ();
}
same1: <T> type<T, T> = { // 1: Have the specialized arguments defined in the type.
  this: std::true_type = ();
}

same1: <T> specialize <T, T> type = { // 2: Have a new keyword 'specialize' which defines the specialization 
  this: std::true_type = ();
}
same1: <T>  type = { 
  specialize: same1 = <T, T> // 3: Have a new keyword 'specialize' which defines the specialization in the body.
  this: std::true_type = ();
}
same1: <T>  type = { 
  same1: type == <T, T> // 4: Use the class name for the specialization.
  this: std::true_type = ();
}

I think 1. would have problems with the specialization of function templates.
2 keeps the specialization near the current location.
3 would be more in line with the new definition of inheritance.
4 might be the weakest one.

My preference would be 2.

@JohelEGP
Copy link
Contributor

JohelEGP commented May 31, 2023

I think the original expected syntax is consistent with Cpp2's way of "declaration follows use".
same<T, T>: <T> (specialization declaration) matches
same<int, int> (use).
same: <T, U> (primary declaration) matches
same<i32, i64> (use).

same1: <T> type<T, T> = { // 1: Have the specialized arguments defined in the type.
  this: std::true_type = ();
}

Originally, this is what I was going to suggest.
Eventually, I noticed it'd be inconsistent with variable templates,
which don't have something like type to put the template-parameter-list.

same1: <T> specialize <T, T> type = { // 2: Have a new keyword 'specialize' which defines the specialization
  this: std::true_type = ();
}

To keep the template-parameter-list upfront, it could be:
Actually, first comes the template-argument-list (as lowered to Cpp1) before the template-parameter-list.
Which matches "declaration follows use".

same1: <T, T> for <T> type = {
  this: std::true_type = ();
}

@MaxSagebaum
Copy link
Contributor

Actually, first comes the template-argument-list (as lowered to Cpp1) before the template-parameter-list. Which matches "declaration follows use".

same1: <T, T> for <T> type = {
  this: std::true_type = ();
}

I think in this example the declaration follows use would be broken.
First you declare the type, then the template-argument-list and afterwards the template-parameter-list. But the templates in the template-parameter-list would be used in the template-argument-list before they are declared.

In

same1: <T> specialize <T, T> type = { // 2: Have a new keyword 'specialize' which defines the specialization
  this: std::true_type = ();
}

we would follow the left to right principle:
We declare a something with the name same1 it has the template-parameter-list <T> and is a specialization of the original template-parameters with the arguments <T, T> which is a type and has the definition ....

For a function it would look like:

f: <T> (a: T) -> T = { return 2 * a ; }

f: specialize <std::string> (a: std::string) -> std::string = {return a + a;}

@JohelEGP
Copy link
Contributor

IIUC, it seems like it comes down to choosing tradeoffs.

Another thing to consider when choosing the syntax
is that an specialization only need to match the primary's template-parameter-list.

t: @value <T> = { }
t<i32>: <> = { } // No metafunction.

@JohelEGP
Copy link
Contributor

I like this, and will try to implement it.
We can add any prefix keyword to introduce the template arguments of the specialization later.

//G template-specialization-argument-list:
//G     '<' template-argument-list? '>'
//G
//G unnamed-declaration:
//G     ':' meta-functions-list? template-parameter-declaration-list? function-type requires-clause? '=' statement
//G     ':' meta-functions-list? template-parameter-declaration-list? template-specialization-argument-list? type-id? requires-clause? '=' statement
//G     ':' meta-functions-list? template-parameter-declaration-list? type-id
//G     ':' meta-functions-list? template-parameter-declaration-list? template-specialization-argument-list? 'final'? 'type' requires-clause? '=' statement
//G     ':' 'namespace' '=' statement

@JohelEGP
Copy link
Contributor

JohelEGP commented Aug 17, 2023

I'm going with the type prefix, which is much easier to parse.

Solved in the PR

There's an unsolvable ambiguity at the grammar level.
You can't make a full specialization that uses a non-templated metafunction,
since the <> is part of the metafunction's identifier.

t: @struct <> type<i32> type = { } // error: `struct<>` is not a known metafunction.

@JohelEGP JohelEGP linked a pull request Aug 17, 2023 that will close this issue
@JohelEGP
Copy link
Contributor

JohelEGP commented Feb 1, 2024

If the obvious syntax is problematic,
how about this specialization-declaration instead?

var_name<t_args>: type = value;   // Obvious syntax (challenging to lex/parse).
var_name: <t_args>: type = value; // Proposed syntax.
+//G specialization-declaration:
+//G     template-parameter-declaration-list ':' unnamed-declaration
+//G     template-parameter-declaration-list ':' alias
 //G
 //G unnamed-declaration:
 //G     ':' meta-functions-list? template-parameter-declaration-list? function-type requires-clause? '=' statement
 //G     ':' meta-functions-list? template-parameter-declaration-list? function-type statement
 //G     ':' meta-functions-list? template-parameter-declaration-list? type-id? requires-clause? '=' statement
 //G     ':' meta-functions-list? template-parameter-declaration-list? type-id
 //G     ':' meta-functions-list? template-parameter-declaration-list? 'final'? 'type' requires-clause? '=' statement
+//G     ':' specialization-declaration
 //G     ':' 'namespace' '=' statement
 //G
 //G alias:
 //G     ':' template-parameter-declaration-list? 'type' requires-clause? '==' type-id ';'
 //G     ':' 'namespace' '==' id-expression ';'
 //G     ':' template-parameter-declaration-list? type-id? requires-clause? '==' expression ';'
+//G     ':' specialization-declaration

Or:

 //G declaration:
 //G     access-specifier? identifier '...'? unnamed-declaration
 //G     access-specifier? identifier alias
+//G     access-specifier? identifier '...'? specialization-declaration

@JohelEGP
Copy link
Contributor

As with past issues, the only problem with the "obvious" syntax is that it doesn't work in the global namespace.
I can live with this:

std::common_type<my_type, my_type>: @struct type { type: type == my_type; }                 // Illegal
std: namespace = { common_type<my_type, my_type>: @struct type { type: type == my_type; } } // Legal

This will mostly come up in tests or slide code, where it's usual to not have namespaces.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants