GabrielDosReis/ipr

Bodies of the mapping expressions (and Variable Templates)

Opened this issue · 18 comments

IPR uses mapping expression to represent functions and templates.
A mapping has a parameter list, type and a body.

The parameter_list of the mapping hosts a homogeneous scope/region that holds parameter declarations. Body of the function mapping is a block that takes an enclosing region (parameter_list scope/region) in its constructor.

Similarly, for a class template, the body of a mapping is an ipr::impl::Class that takes the enclosing region in its constructor.

For a function template, the body is a mapping expression for a function. Function mapping takes enclosing region in its constructor.

From the ipr interface, we have a couple of alternatives on how to represent the body of the mapping. Option 1 is to use the actual expression that will become the body/initializer of the entity after substitution. Option 2 is to create a full fledged declaration and to make it the body of the mapping.

Template Kind Option 1 Option 2
Function template Function Mapping Expr ipr::Fundecl
Class Template Class (Type) ipr::Typedecl
Alias Template Type ipr::Alias (Decl)
Variable Template ??? ipr::Var (Decl)

While Option 2 is possible from the perspective of ipr interface. It is cannot be created using ipr::impl as it exists today.
To create a declaration, we have to have a heterogeneous scope in which to create it. We don't have it. The enclosing scope of the body of the mapping is a homogenous parameter scope.

Option 1 is feasible for function, class and alias templates, but, does not have a node (that is not a declaration) that can represent the body of the mapping for a variable template.

Option 2 would need a resolution of where ipr::Fundecl, ipr::Typedecl, ipr::Alias or ipr::Var will be stored.

One alternative how option 2 can be addressed is to add a storage for declarations that can be bodies of templates (ipr::impl::Var at the minimum, or all four flavors) and host them in the master_decl_data<ipr::Template>

      template<>
      struct master_decl_data<ipr::Template>
         : basic_decl_data<ipr::Template>, overload_entry {
         using Base = basic_decl_data<ipr::Template>;
         // The declaration that is considered to be the definition.
         Optional<ipr::Template> def { };
         Optional<ipr::Linkage> langlinkage { };
         const ipr::Template* primary;
         const ipr::Region* home;

         // The overload set that contains this master declaration.  It
         // shall be set at the time the node for the master declaration
         // is created.
         impl::Overload* overload;

         // Sequence of specializations
         decl_sequence specs;

         // +++++++++++ ????????
         decl_factory<ipr::Var> vars;
         // ++++++++++++++????

         master_decl_data(impl::Overload*, const ipr::Type&);
      };

Option 2 is a bit wasteful. We do not need entire declaration machinery that deals with overloads and master declarations, since every declaration that is a body of the mapping is unique to that mapping, since it refers to parameters of that particular mapping.

Making Option 1 work for all template kinds needs some kind of node to represent a body of a variable template. Essentially a pair (type and initializer).

I suspect the model behind IPR has oscillated between what you call Option 1 and Option 2, with a few exceptions:

  1. "alias template'' has never been thought of as a "template'' from the very beginning and design of the feature. So, it has never been thought of that in Option 2 that it would generate an ipr::Alias. The renaming of "template alias" to "alias template" was the result of sequence of unfortunate events and misunderstanding after the feature was adopted, and that hasty renaming (in the name of some misguided "consistency") has never ceased to create confusion.
    Summary: In both options 1 and 2, the template alias itself is to be represented as a ipr::Alias, not a ipr::Template, the initializer of that ipr::Alias is a ipr::Forall node.

  2. In option 1, what are the reasons why the initializer (to be substituted into) cannot be used as the initializer of the mapping?

  3. In option 2, which is the thinking that came in much later in the development of the IPR one need something like a where-expression (#113) to achieve full fidelity

Variable Templates

I see, your point. We can represent variable template in option 1, say,

template <typename T, typename U>
T var = U{};

as a Mapping(<T x U>, type: T, init: U{})

We infer that it is a variable template by virtue of the mapping type being not: Function(X->Y), nor ClassType, nor TypenameType (though this is unnecessary if we represent template alias as ipr::Alias).

I have a bit of unease with this variable template representation, since, unlike Function or Class template, where initializer make sense on its own, the variable template requires understanding of this convention to interpret it properly, i.e. manufacture a variable of type X with initializer Y, where X is a type of the mapping and Y is an initializer/body of the mapping.

Though, on further reflection it is no different than other templates. The interpretation of the mapping after substitution is
a) manufacture an entity of the type of the mapping
b) initialize it with the initializer of the mapping.

Template Alias

With regard to:

the initializer of that ipr::Alias is a ipr::Forall node.

I think you meant initializer for the alias is a mapping of the ipr::Forall type. If it is just ipr::Forall type, we have no place to specify/introduce parameters.

template <typename T>
using vec = vector<T>;

Translates into:

vec: Alias = mapping(<T>, init:vector<T>)

The type of the alias and the mapping itself will be the type ipr::Forall(T, vector<T>).
At the moment, mapping is the only construct in the IPR that can introduce parameters.

We infer that it is a variable template by virtue of the mapping type being not: Function(X->Y), nor ClassType, nor TypenameType (though this is unnecessary if we represent template alias as ipr::Alias).

Using your syntax and your example

template <typename T, typename U>
T var = U{};

in option 1, I expect the Mapping(<T, U>, type: Forall(T: typename, U: typename). T, init: U{})

I am still not quite sure why the initializer does not make sense on its own?

vec: Alias = mapping(<T>, init:vector<T>)

Yes

I am still not quite sure why the initializer does not make sense on its own?

Initializer only tells us what to initialize the variable with, it does not tell us what would be the type of the variable.

struct Five { operator int() { return 5; } };

template <typename T, typename U>
T var = U{};

int main()
{
    return var<int, Five>;
}

I cannot infer the resulting type of var<int, Five> by examining the initializer, I need to know the type of the variable, which I need to derive from the type of the mapping

Initializer only tells us what to initialize the variable with, it does not tell us what would be the type of the variable.

Exactly, and that is totally fine because this is a template! :-)

I cannot infer the resulting type of var<int, Five> by examining the initializer,

But you know it cannot be the 2 out of the three possibilities. You don't need to know the type of the variable to arrive to that conclusion.

But, you do have the type of the variable: it is the return type of the mapping. See my previous comment and the type I indicated and that I expect.

Oh, I agree that this mechanism work. By combining the resulting type of the mapping and initializer we have all we need to instantiate a variable template and also the type of the mapping allows us to disambiguate it from class and function templates.
My comment:

the initializer does not make sense on its own.

was referring to aesthetics that it stands out from function and class templates where initializer completely describes the entity to be instantiated (This is in the Option 1 world. In Option 2, it is all nice and uniform).

One extra limitation of Option 1 in today's IPR is that we don't have a good way to represent a declaration of a function template:

template <typename T> T f(T x = T{});

Due to the mapping property that it always must have an initializer.
There was a partial solution added recently by allowing ipr::Fundecl to have a variant that stores a parameter_list (for a declaration) or a mapping (for the definition). That does not help in Option 1 world, since, we cannot use Fundecl as an initializer to the template mapping.

Issue #86 touches on the point made by Gor about template function decls.

Also, it seems that Option 1 as we are exploring here does not work completely for variable templates since initializer could be absent (and mapping does not allow to have an optional result).

template <typename T> T var;

would map to:

Mapping(<T, U>, type: Forall(T: typename, U: typename). T, init: <nothing>)

and this is not allowed by the current mapping interface which expects Mapping::result() to be always available.

Also, it seems that Option 1 as we are exploring here does not work completely for variable templates since initializer could be absent (and mapping does not allow to have an optional result).

Actually, this is case of a definition where the initializer is (syntactically) missing, and that is the case where you can use a Phantom node to indicate the expression is not there -- just like in the case of an array of unknown bound (int a[];), or the case of an empty statement (;), or a return statement with missing expression (return;). So, that case is covered:

Mapping(<T, U>, type: Forall(T: typename, U: typename). T, init: Phantom)

Option 2 is what the current IFC spec/implementation does, and I am not satisfied with it either.

I believe the where-expression gets us a better representation than option 1 and option 2. The challenge is "can we get it efficiently too?"

See also #86

Another question about Option 1 for variable templates is where would we store arguments for partial and explicit specializations.

ipr::impl::Template has an args field, but it is not exposed via ipr::Template interface.

template <typename T, typename U>
T var;

template <typename T>
T var<T,int> = 4;

template <> int 
var<double,int> = 8;

Would it be in the name argument of the declare_secondary_template member function?

as in

auto primary = region->declare_primary_template("var", ...);
...
auto var1 = region -> declare_secondary_template(Template_id(var<T,int>), ...);
auto var2 = region -> declare_secondary_template(Template_id(var<double,int>), ...);
primary-> ... specs.insert(var1);
primary-> ... specs.insert(var2);

where T in var1 refers to a parameter in a mapping that is used to initializer var1.

Another question about Option 1 for variable templates is where would we store arguments for partial and explicit specializations.

This question touches on another important point in the original design of the IPR that I now see I never explained in the IPR paper.

The IPR takes on generalization of a few concepts of Standard C++ and sought to cast them in a way that was more general. To name a few:

  1. Expressions, hence values, includes types; declarations are expressions
  2. Types, e.g. namespaces are UDT; function templates have types (something Standard C++ managed to define)
  3. Scopes are just sequence of declarations, not specifically indicating declarative regions;
  4. etc.

IPR decided to rely on types of expressions and declarations to account for various hacks in Standard C++, including the struct stat hack (a time-honored C hack from the old days) where by a function name can "hide" a type-name and you have to use an elaborated type-specifier (e.g. struct stat) to get the type -- for example the function stat() returns a struct stat*. Using the concept of overload, the IPR declares that the name stat is overloaded, with an entry that has class as type, and another that has a function type. This is an important point in the design. Next, partial specializations and explicit specializations are now viewed as overloads; furthermore, if there ever was a proposal to add function template partial specializations (back in the late 1990s and early 2000s, that was a recurring request), the IPR was prepared.

The reason why that background is important is to understand how partial specializations -- not just of variable templates, but of any templates -- are to be represented. In the example we have:

  1. The primary template
template <typename T, typename U>
T var;

has type var: Forall(T: typename, U: typename) . T.

  1. The partial partial specialization
template <typename T>
T var<T,int> = 4;

has type var: Forall(T: typename) . T

  1. And the explicit specialization
template <> int 
var<double,int> = 8;

has type var: Forall() . int

They are all chained together in the overload set. That is how templates (and any other declarations) were to be represented. It was a much regular and system. Of course you would need some special rule (algorithms) projecting the barnacles of Standard C++ onto this general representation to get the result of your name lookup.

If we're concerned with exactly mimicking the barnacles of Standard C++, we can keep the algebraic structure of the data structures and have dedicated names and accessors designed to reflect exactly those. But, the reliance on types to drive the design allow for simpler representations. We need to verify that the addition of variable templates (C++14) didn't violate some of the hypothesis at the basis of the original design. And also, how partial ordering works in this scheme without too much trouble.

The term secondary template refers to secondary declaration which are not the first declaration of the master declaration. They are not for partial specializations. The explicit specialization master_decl_data<ipr::Named_map> is an overload set entry for that reason. The field specs is for implicit specializations and explicit instantiations of a given template.

Thank you for the write up. That was very helpful. Another part of my question is where to capture the argument list of the partial or explicit specialization. I filed another issue on this #129 .

When we discussed this earlier in the context of class templates, the idea was to use id field of an ipr::Class to store the Template_id that would both indicate the primary template and an argument list.

For variable templates, we do not have an id to play with and therefore no place to store the arguments.

With regard to:

The term secondary template refers to secondary declaration which are not the first declaration of the master declaration. They are not for partial specializations.

This statement seems to contradict what was stated in the IPR user-guide and its examples. (Note that we recently renamed named mappings to templates in the IPR)

11.2 Secondary named mappings
A secondary template declaration provides a specialized implementation
for given sub-family of the primary template. Therefore, it may introduce
more or fewer parameters than the primary template does. However, it
must supply an argument list that meets the requirements (in number and
kinds) stated by the primary template declaration. Secondary templates
do not participate in overload resolution.

struct Array<void*> { // #4
// ...
};

template<class T>
struct Array<T*> : Array<void*> { // #5
// ...
};

The declaration #4 is an explicit specialization of Array<>, but it is not a
template. Therefore, it must be represented as an ordinary class. On the
other hand, #5 is a secondary template declaration. It specializes #1, and
uses the argument list <T*>.

My interpretation of the user-guide is that make_secondary_map (now renamed to make_secondary_template was intended to be used for partial specializations.

Update regarding Option 2: this requires either a where-expression (#113 ), or the implementation of ipr::Template to contain some form of ipr::Region where the current instantiation is stored. Furthermore, such a region could also store other specialization of that template. In that case, the corresponding ipr::Region is no longer a 1:1 representation of a textual region in the template declaration, but an extended form of it where the extension is used to declare (implicit) specializations.

A corollary of Option 2 is that a ipr::Template always generates a declaration. This is probably OK since a ipr::Mapping is the more general form that also yields other non-declaration forms.

A corollary of Option 2 is that a ipr::Template always generates a declaration.

Actually, that is not true: a parameterized lambda, e.g.

[]<typename T>(T x, T y) { return x |  y; }

is in fact template of a lambda. That is, it should be a ipr::Template whose result() is an ipr::Lambda, which is an expression, not a declaration.

A corollary of Option 2 is that a ipr::Template always generates a declaration.

Actually, that is not true: a parameterized lambda, e.g.

[]<typename T>(T x, T y) { return x |  y; }

[...] is in fact template of a lambda. That is, it should be a ipr::Template whose result() is an ipr::Lambda, which is an expression, not a declaration.

If we go with Option 2, we would just need to call these something like ipr::Lambda_template, and reserve ipr::Template for a parameterized declaration -- alias templates are already taken care off as not being template.