www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - std.traits.ParameterIdentifierTuple producing empty results.

reply Carl Sturtivant <sturtivant gmail.com> writes:
In the new world of ImportC giving direct access to C in D 2.108, 
and function pointer types and function types being imported into 
D source automatically from the D compilation of C headers, there 
is a case to be made for a maximum of information about the 
declarations in those headers being made available to D. 
Specifically the parameter names in declarations of functions 
(and function pointers) in such headers.

Right now, given a function's signature, 
[`std.traits.ParameterIdentifierTuple`](https://dlang.org/phobos/std_traits.html#Param
terIdentifierTuple) produces the names of its parameters at compile time.
```D
int foo(int num, string name, int);
static assert([ParameterIdentifierTuple!foo] == ["num", "name", 
""]);
```
However, there is no way (so far as I have been able to 
ascertain) to extend this behavior to some frequently occurring 
other ways function names may be associated with signatures in C 
header files.

A common example is a Vtable defined in a C header file, being 
the C representation of an OOP-something defined for C++. When 
compiled with ImportC, as `__cplusplus` is not `#define`d, this 
is what D gets made available automatically.

In effect this is a struct with the field names being the names 
of function pointers each of which is given a signature that 
contains parameter names. In effect the signatures of several 
functions are given whose names are the names of the struct's 
fields.

In this situation, right now ImportC does indeed preserve the 
names of the parameters, just as it does for the vanilla function 
foo above. But as the names are struct field names, there is no 
name that is the name in a signature, and 
`ParameterIdentifierTuple` has no function name to be applied to, 
making the parameter names inaccessible.

How was I able to say that ImportC preserves those names? It is 
still possible to get the *type* of the function pointed to by a 
struct field and see it using `pragma(msg,_)` and the parameter 
names are present: good news, for those of us trying to get the 
parameter names from a signature as a practical matter.  However, 
applying `ParameterIdentifierTuple` to a function *type* (or a 
function pointer type too) produces no names, despite the above 
indicating they are recorded in the type.

Parameter names and their connection to the D types of function 
pointers and functions is a vexed question. On the one hand, 
parameter names do not affect type equality, so a purist may 
argue they should not be a part of the type. The force of this is 
blunted somewhat by the fact that we may simply *define* type 
equality to ignore parameter names. Yet the parameter names 
indicate something about the source of the function type 
definition that is in this sense not a part of the type. This is 
ugly.

As a practical matter right now, it would be good if parameter 
names continue to be stored in the type of a function or function 
pointer, as this is (so far as I have been able to ascertain) the 
only way they are accessible in D. Right now I am extracting them 
using CTFE!

Again, as a practical matter, it would be good if he purist view 
of function and function pointer types could be put aside and 
`ParameterIdentifierTuple` be permitted to work on function and 
function pointer types, at least until there is an alternative 
way for compile-time retrieval of parameter names from ImportC. 
Is there an operational downside to this?

What could an alternative way to get parameter names from ImportC 
be? Either existent now, or a language addition?
Apr 01
parent reply Nick Treleaven <nick geany.org> writes:
On Monday, 1 April 2024 at 15:57:23 UTC, Carl Sturtivant wrote:
 As a practical matter right now, it would be good if parameter 
 names continue to be stored in the type of a function or 
 function pointer, as this is (so far as I have been able to 
 ascertain) the only way they are accessible in D. Right now I 
 am extracting them using CTFE!

 Again, as a practical matter, it would be good if he purist 
 view of function and function pointer types could be put aside 
 and `ParameterIdentifierTuple` be permitted to work on function 
 and function pointer types,
I don't think it's possible to make the `is(__parameters)` check work there without changing the language. Parameter names in a function type seem to be lost once that type is used to instantiate a template: ```d void f(T)() { static if (is(T PT == __parameters)) pragma(msg, PT); } void foo(int i, char c); void main() { static if (is(typeof(foo) PT == __parameters)) pragma(msg, PT); f!(typeof(foo)); } ``` Output: ``` (int i, char c) (int, char) ``` And that makes sense because otherwise a function type with different parameter names would need to instantiate a separate instance of the template in order to preserve the identifiers. So if that happened, function types with different identifiers would not always compare equal as types.
Apr 01
parent reply Carl Sturtivant <sturtivant gmail.com> writes:
On Monday, 1 April 2024 at 18:20:43 UTC, Nick Treleaven wrote:
 I don't think it's possible to make the `is(__parameters)` 
 check work there without changing the language. Parameter names 
 in a function type seem to be lost once that type is used to 
 instantiate a template:

 ```d
 void f(T)() {
     static if (is(T PT == __parameters))
         pragma(msg, PT);
 }
 void foo(int i, char c);
 void main()
 {
     static if (is(typeof(foo) PT == __parameters))
         pragma(msg, PT);

     f!(typeof(foo));
 }
 ```
 Output:
 ```
 (int i, char c)
 (int, char)
 ```
 And that makes sense because otherwise a function type with 
 different parameter names would need to instantiate a separate 
 instance of the template in order to preserve the identifiers. 
 So if that happened, function types with different identifiers 
 would not always compare equal as types.
Very interesting and pertinent! Thank you. I looked into `is()` with `__parameters` and the documentation is a bit sparse, however eventually I found that I could extract the parameter names without CTFE provided I avoided stripping them out through instantiating a template with them (following your conclusion above). I found [this posting](https://forum.dlang.org/post/vpjpqfiqxkmeavtxhyla forum.dlang.org) by one of the forum's great authors that showed the way after that. Here is what I eventually arrived at, where `PROTO` is a function type with parameter names in it. ```D static if( is(PROTO Params == __parameters) ) { enum paramNames = mixin("["~RequestParams!(Params.length)~"]"); pragma(msg, paramNames.stringof); ``` Here the trick is to have the template `RequestParams` produce a string containing a comma separated series of substrings of the form `__traits(identifier,Params[?..?])` where the slice is [0..1], [1..2], ..., i.e. one for each parameter, so that when mixed in `Params` is asked to provide an array of parameter names where each parameter in turn is given by a single element slice. Just using an index only produces the type of a parameter, but taking slices of any size seems to keep the parameter names. Similar name-discarding behavior is found when static foreach iterates over `Params`, implicitly indexing it. I naively defined `RequestParams` recursively, using `std.format.format` to substitute indexes. ```D template RequestParams(int N) { static if( N==0 ) enum RequestParams = ""; else enum RequestParams = RequestParams!(N-1)~ format("__traits(identifier,Params[%s..%s]), ", N-1, N); } ```
Apr 01
parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 2 April 2024 at 00:28:50 UTC, Carl Sturtivant wrote:
 I looked into `is()` with `__parameters` and the documentation 
 is a bit sparse, however eventually I found that I could 
 extract the parameter names without CTFE provided I avoided 
 stripping them out through instantiating a template with them
I realized there's another way, inspired by your `extern FunctionType` idea: ```d import std.traits; void foo(int i, char c); void main() { pragma(msg, ParameterIdentifierTuple!foo); // i, c alias Foo = typeof(foo); pragma(msg, ParameterIdentifierTuple!Foo); // no identifiers static if (is(Foo PS == __parameters)) void f(PS); pragma(msg, ParameterIdentifierTuple!f); // i, c } ```
Apr 03
parent Carl Sturtivant <sturtivant gmail.com> writes:
On Wednesday, 3 April 2024 at 10:13:56 UTC, Nick Treleaven wrote:
 I realized there's another way, inspired by your `extern 
 FunctionType` idea:

 ```d
 import std.traits;

 void foo(int i, char c);

 void main()
 {
     pragma(msg, ParameterIdentifierTuple!foo); // i, c
     alias Foo = typeof(foo);
     pragma(msg, ParameterIdentifierTuple!Foo); // no identifiers
     static if (is(Foo PS == __parameters))
         void f(PS);
     pragma(msg, ParameterIdentifierTuple!f); // i, c
 }
 ```
A neat construction! You get a function (name) of the given function type! This is the much needed crack in the wall. Very handy that the bizarre __parameters tuple PS permits the declaration of a function with those parameters including their names. So at least at the point where a function type has been created this technique can produce the names inline with no coding complications. Many thanks.
Apr 03