Skip to content

C++ Interop: How to map reference types (T&) vs. pointer types (T*)? #6291

@bricknerb

Description

@bricknerb

Summary of issue:

This question is about the C++ interoperability mapping for reference types (T&) versus pointer types (T*), specifically concerning C++'s type system rules for template instantiation and overload resolution.

In C++, T* and T& are distinct types. They resolve differently for function overloads and create different template specializations. Carbon's proposed mapping seems to be based on semantics (like nullability) rather than preserving this fundamental type distinction. For example, C++ T& (non-nullable) and C++ T* _Nonnull map to Carbon T* (non-nullable), while C++ T* (nullable) maps to Carbon T*?.

How will this semantic mapping preserve the type identity required for high-fidelity interop? Specifically, how will Carbon distinguish between an imported C++ template specialization for MyTemplate<Foo*> versus MyTemplate<Foo&>?

Details:

In C++, the distinction between a pointer and a reference is not just syntactic; it is fundamental to the type system.

  1. Overload Resolution: A function can be overloaded on pointer vs. reference types. These are unambiguous and distinct function signatures:
    void CppFunc(MyType* p);
    void CppFunc(MyType& r);

  2. Template Instantiation: A template specialized on a pointer type is a completely different type from one specialized on a reference type:
    template<typename T> class CppTemplate {... };
    CppTemplate<MyType*> and CppTemplate<MyType&> are two unique, incompatible types.

Carbon's interoperability goal is to handle C++'s complex features, including templates and operator overloading. However, the current mapping proposals seem to create a conflict:

  • C++ MyType& (non-nullable reference) and C++ MyType* _Nonnull (non-nullable pointer) map to Carbon MyType* (non-nullable pointer).
  • C++ MyType* (nullable pointer) maps to Carbon MyType*? (nullable pointer).

This mapping is based on nullability, but it seems to erase the C++ distinction between a pointer and a reference. This raises critical questions for bi-directional interoperability:

  • Importing Templates: When Carbon imports CppTemplate, how will it represent the difference between Cpp.CppTemplate(MyType*) (from CppTemplate<MyType&>) and Cpp.CppTemplate(MyType*?) (from CppTemplate<MyType*>)? Is this the intended mapping?

  • Calling Overloads: If Carbon imports the overloaded CppFunc from the example above, what will the Carbon signatures be?

    • Will void CppFunc(MyType& r); become fn CppFunc(r: MyType*)?
    • Will void CppFunc(MyType* p); become fn CppFunc(p: MyType*?)?
    • If so, how does a Carbon user call the C++ MyType* overload? Do they have to pass a MyType*? value, even if they know their pointer isn't null?

This seems to break the "seamless" and "unsurprising" mapping goal. We need a clear model for how Carbon's type system will preserve the distinct identities of C++ pointer and reference types to correctly handle template specializations and function overloads.

Any other information that you want to share?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    leads questionA question for the leads team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions