- Proposal: 0007
- Author(s): Chris Bieneman
- Sponsor: TBD
- Status: Under Consideration
- Planned Version: 202y
HLSL does not currently support const
non-static
member functions for user-defined data
types. This proposal seeks to add support for const
non-static
member functions, and to
adopt const-correct behaviors across the HLSL library objects.
The absence of const
non-static
member functions causes some challenges since HLSL
injected ASTs do have const
and non-const
non-static
member functions. Further, since
variables can be const
-qualified, without the ability to specify const
there
are some cases that cannot be worked around without breaking
const
-correctness.
DXC has received a number of issues relating to this. Two recent issues are listed below:
In the first issue, a user defined data type's functions are basically unusable if
the data type is placed in a ConstantBuffer
. This results from the
ConstantBuffer
access returning const &
objects.
The second issue describes a similar problem, where overloaded operators on
instances of const
-qualified user-defined types are unusable.
Following C++, HLSL will enable support for const
non-static
member functions and
instance operator overloads (henceforth collectively referred to as constant
member functions) to allow execution of member functions on const
objects and
preserve const
qualifiers.
Updates to HLSL's built-in data types to observe best practices in const-correctness will follow the introduction of language support. Functions which return mutable lvalue references will become non-constant member functions, and functions which return constant lvalue references or object values will become constant member functions.
C++'s existing syntax for declaring constant instance functions is compatible
with HLSL. Adoption of this syntax does not introduce any syntactic ambiguities
with existing HLSL constructs. Adding the const
keyword to the end of the
function declarator before the optional function body will denote a const
member function function. The const
keyword applies to the implicit object
argument so it can only be applied to non-static
member functions. See the
examples below defining both a constant function and a constant overload of the
call ()
operator:
struct Pupper {
void Wag() const { /* body omitted */ }
void operator() const { /* body omitted */ }
};
In a const member function, the implicit object parameter (this
) becomes a
constant lvalue reference (const &
). Code modifying any field of the this
object is ill-formed and will produce a diagnostic. Calls to non-constant member
functions are also ill-formed and will produce a diagnostic.
This change requires modifications to HLSL's overload resolution rules to account for the const-ness of object parameters. When performing lookup of possible overload candidates, overloaded functions with non-constant implicit object parameters are invalid candidates when the implicit object is constant.
Standard HLSL argument promotion rules will apply for the object parameter, but
they cannot remove the const
qualifier and shall not convert from a constant
lvalue to a non-constant rvalue by copying the implicit argument as is valid for
other arguments.
Introducing constant member functions provides an opportunity to revisit the const-correctness patterns of existing HLSL data types. With this change we will perform an audit of existing data types to provide constant and non-constant member functions as appropriate for the data type.
When applied to HLSL intangible types, the const
qualifier will apply as if to
the handle, not the data the handle grants access to. For example, a const RWBuffer<T>
will still allow writes to the underlying resource, however the
resource variable itself cannot be re-assigned.
Supporting constant member function overload resolution will break existing code
that calls member functions on cbuffer
, tbuffer
or global constant variables.
Consider the following valid HLSL:
struct Hat {
int getFeathers() {
return Feathers;
}
int Feathers;
};
cbuffer CB {
Hat H;
};
export int GetFeatherCount() {
return H.getFeathers();
}
This code is valid under HLSL 2021 because HLSL ignores the const-ness of the implicit object parameter. On introducing constant member functions, this code is ill-formed and will produce a diagnostic.
The code is ill-formed because these declarations inside a cbuffer
are
implicitly constant. Today we ignore the const
-ness of the object parameter
and resolve the function.
Implementing const-correct member functions on built-in HLSL data types should have no disruption to users.
Consider the following code:
void setValue(RWBuffer<int> R, int Val, int Index) {
R[Index] = Val;
}
void setValueConst(const RWBuffer<int> R, int Val, int Index) {
setValue(R, Val, Index);
}
In setValueConst
, the const
qualifier applies to the instance of the
RWBuffer
parameter. A new RWBuffer<T>
variable can be created from a const RWBuffer<T>
via copy-initialization (standard copy construction), allowing
setValueConst
to call setValue
. This does not violate const-correctness
since the handle is treated as const while the data it references is not.
TBD: HLSL's current overload resolution rules are not fully codified anywhere I'm aware of. For this design to be complete we need to fully specify the overload resolution and standard argument conversions.
This change should have no impact on code generation through SPIR-V or DXIL assuming that the existing parameter mangling for constant implicit object parameters works as expected.