类型实例化可以与值实例化分开吗?

Can type instantiation be separated from value instantiation?

在 C++ 中,我可以在编译时实例化一个泛型类型,然后在运行时构造它:

struct Vertex {};
struct Edge   {};

template<typename NodeType, typename IdType>
struct Wrapper {
  IdType id;

  Wrapper(IdType id) : id{id} {};
};

int main() {
  using vertexWrapper = Wrapper<Vertex, int>;
  vertexWrapper v(3);
}

变量明确分开,类型从不 look/feel 像值。我正在尝试在教堂做类似的事情:

record Vertex {}
record Edge   {}

record Wrapper {
  type nodeType;
  type idType;   
  var id: idType;

  proc init(id) {
    this.id = id;
  }
}

type vertexWrapper = Wrapper(Vertex, int);

var v1 = 3: vertexWrapper;

当我编译这段代码时,我得到:

chpl -o graph-add-inclusion --cc-warnings test.chpl
test.chpl:9: In initializer:
test.chpl:10: error: can't omit initialization of field "nodeType", no type or default value provided
test.chpl:10: error: can't omit initialization of field "idType", no type or default value provided

有没有一种方法可以将 Chapel 中的类型构造与值构造分开,以实现我在示例中尝试获得的标记类型的效果?我使用标记类型来实现两种实体(此处为顶点和边)通用的单一实现,但我希望这些实体是不同的类型。

还有一个相关的问题。我应该只写:

type vertexWrapper = Wrapper(Vertex);

然后从我的构造函数中单独推导出整数?

似乎在定义时检查了构造函数,不可能将类型与值分开提供。我做对了吗?如果我做对了,这在未来会改变吗?

您遇到的问题是您定义的初始值设定项,而不是您使用的类型别名。由于您定义它的方式,可以想象用户可以尝试这样写:

var x = new Wrapper(1); // type is inferred from the new's return

并且初始化器不知道如何处理 nodeType 和 idType 字段。所以你的初始化器需要在它的主体中明确地设置它们。

我同意在您使用实例化时让初始化程序计算出它们的值会很好,并且预计这是我们将来会支持的内容。在短期内,您几乎可以通过一点点重复工作来获得您想要的东西。

为此,首先您可以更新初始化程序,以便它可以根据相应的参数计算出 idType。

proc init(id) {
  idType = id.type;
  this.id = id;
}

但是,这对 nodeType 字段没有帮助,因为在您所描述的内容中没有初始值设定项可以依赖的相应值字段。这意味着您必须手动将其提供给初始化程序。您可以通过访问类型别名的 nodeType 字段以一般方式执行此操作:

var v1 = new vertexWrapper(vertexWrapper.nodeType, 3);

但是您还需要更新初始化程序以将其作为参数。所以创建实例 v1 必须看起来像这样:

record Vertex {}
record Edge   {}

record Wrapper {
  type nodeType;
  type idType;   
  var id: idType;

  proc init(type nodeType, id) {
    this.nodeType = nodeType;
    this.idType = id.type;
    this.id = id;
  }
}

type vertexWrapper = Wrapper(Vertex, int);

var v1 = new vertexWrapper(vertexWrapper.nodeType, 3);

希望这是一个在短期内适用于您的用例的解决方案。

Should I be able to just write:

type vertexWrapper = Wrapper(Vertex);

and then have integer deduced separately from my constructor?

我们目前不支持部分实例化,所以 Wrapper(Vertex) 不是你可以写的东西。如果你想将idType设置为像int这样的普通值,你可以将它作为默认值提供给类型字段:

type idType = int;

这将允许您仅通过 Wrapper(Vertex) 指定 nodeType,但这意味着 idType 被锁定为 int 除非您使用 new (var x = new Wrapper(Vertex, 3.0);) 创建实例化,或者除非您指定其他内容同时与 nodeType.

作为这个答案的序言(正如我在对这个问题的评论中所指出的),我认为 Chapel 的初始化程序故事应该改进,以便在您尝试时支持对代表实例化泛型的类型别名的初始化程序调用做。

在那之前,我尝试过一个似乎可行的想法 [Try It Online]。本质上,我是通过使用 类型方法 Wrapper 类型本身上创建工厂方法 newNode() — 即,在类型本身上调用的方法比该类型的值:

record Vertex {}
record Edge   {}

record Wrapper {
  type nodeType;
  type idType;
  var id: idType;

  proc type newNode(id) {
    return new Wrapper(nodeType, idType, id);
  }
}

type vertexWrapper = Wrapper(Vertex, int);

var v1 = vertexWrapper.newNode(3);
writeln(v1);

因为 vertexWrapper 类型别名定义了 nodeType,我可以在工厂方法中引用它来创建 Wrapper.

的新实例

我认为这基本上可以被视为封装 Lydia 先前提出的解决方案的另一种方式。