Thread overview
Constraining template with function signature
Jun 08, 2016
Carl Vogel
Jun 09, 2016
cy
Jun 13, 2016
Carl Vogel
June 08, 2016
Hi,

What's the best way, when using a Callable as a template parameter, to constrain it by signature?

For example, if you have a function that sorts an array like so:

T[] sortArray(alias less, T)(T[] arr) { ... }

Then you know that you'd want `less` to have signature (T, T) -> bool.

Now, I can use something like isCallable std.traits to make sure the predicate is a Callable, and there are various function traits in the module that I could combine with `is` clauses to enforce the signature it seems, but that seems very clunky.  Is there a better way?

I note that I don't find many examples of this in phobos, so I'm wondering if there actually is a good solution...

Thanks,
-c.
June 08, 2016
On 6/7/16 9:42 PM, Carl Vogel wrote:
> Hi,
>
> What's the best way, when using a Callable as a template parameter, to
> constrain it by signature?
>
> For example, if you have a function that sorts an array like so:
>
> T[] sortArray(alias less, T)(T[] arr) { ... }
>
> Then you know that you'd want `less` to have signature (T, T) -> bool.
>
> Now, I can use something like isCallable std.traits to make sure the
> predicate is a Callable, and there are various function traits in the
> module that I could combine with `is` clauses to enforce the signature
> it seems, but that seems very clunky.  Is there a better way?
>
> I note that I don't find many examples of this in phobos, so I'm
> wondering if there actually is a good solution...

The de-facto way to do this is to write a lambda, and check that you can call the function inside the lambda, then check to see if that compiles.

e.g.:

if(is(typeof( { bool b = less(T.init, T.init)); } )))

The is(typeof( construct means "does this have a valid type". Should only work if the call is sound.

You can also use __traits(compiles, but for some reason Phobos writers prefer the is(typeof( mechanism (I remember someone saying there is a difference, but I don't know what it is).

-Steve
June 09, 2016
The other way is better, but since you asked...

On Wednesday, 8 June 2016 at 01:42:55 UTC, Carl Vogel wrote:
> Now, I can use something like isCallable std.traits to make sure the predicate is a Callable, and there are various function traits in the module that I could combine with `is` clauses to enforce the signature it seems, but that seems very clunky.  Is there a better way?

You could put all that stuff into one single template like this:

template isCompatibleCallable(alias Src, alias Dest) {
     static assert(isSomeFunction!Src || isCallable!Src,
                                   "Source is not callable");
     static assert(isSomeFunction!Dest || isCallable!Dest,
                                   "Destination is not callable");

  static assert(is(ParameterTypeTuple!Src == ParameterTypeTuple!Dest),
                                "Type Tuples differ");
  pragma(msg,ParameterStorageClassTuple!Src ==
                                ParameterStorageClassTuple!Dest);
  static assert(ParameterStorageClassTuple!Src ==
                                ParameterStorageClassTuple!Dest,
                                "Storage classes differ");
  static assert(is(ReturnType!Src == ReturnType!Dest),
                                "Return type differs");
  immutable bool isCompatibleFunction = true;
}

That works if you have a "default action" when the callable isn't specified, because you can match the Callable to the default one. So like...

bool default_less(T)(T a, T b) { return a < b; }

T[] sortArray(alias less = default_less!T, T)(T[] arr) if(isCompatibleCallable(less,default_less!T) { ... }

But it's probably clearer to use that is(typeof({ how this function will be called })) trick.
June 13, 2016
On Thursday, 9 June 2016 at 19:08:52 UTC, cy wrote:
> But it's probably clearer to use that is(typeof({ how this function will be called })) trick.

A very delayed thanks to both of you. It does seem like it would be useful to have something like a hasSignature!(Fun, Ret, Args...) defined in std.traits. But Steven's solution is pretty good.

Thanks again!