Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow default valued parameters after template variadics #7831

Merged
merged 8 commits into from
Feb 4, 2018

Conversation

timotheecour
Copy link
Contributor

Function parameters with default values are now allowed after variadic template parameters, and always take their default values. This allows using special tokens (eg FILE) after variadic parameters, which was previously impossible.

Eg:

string log(T...)(T a, string file = __FILE__, int line = __LINE__)
{
  return text(file, ":", line, " ", a);
}

assert(log(10, "abc") == text(__FILE__, ":", __LINE__, " 10abc"));

This should be preferred to the previous workaround, which caused template bloat:

string log(string file = __FILE__, int line = __LINE__, T...)(T a);

@dlang-bot
Copy link
Contributor

dlang-bot commented Feb 3, 2018

Thanks for your pull request, @timotheecour! We are looking forward to reviewing it, and you should be hearing from a maintainer soon.

Some tips to help speed things up:

  • smaller, focused PRs are easier to review than big ones

  • try not to mix up refactoring or style changes with bug fixes or feature enhancements

  • provide helpful commit messages explaining the rationale behind each change

Bear in mind that large or tricky changes may require multiple rounds of review and revision.

Please see CONTRIBUTING.md for more information.

Bugzilla references

Auto-close Bugzilla Description
8687 Variadic templates do not work properly with default arguments

Copy link
Member

@wilzbach wilzbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huge fan of this and I'm impressed that's a two line change.
People have been complaining about this on the NG forever and it was cited as one of the reason why logging in D "sucks".
Also

  1. You can add at least https://issues.dlang.org/show_bug.cgi?id=8687 to the fixed bugs.
  2. This needs a PR to dlang.org to update the spec

CC @MartinNowak as he mentioned this on Bugzilla:

While working on limited lifetimes (@safe RC/Uniq) I ran into another use-case where a default argument is needed.

*/

import std.typecons : tuple;
import std.conv : text;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't import Phobos if not absolutely required and you have a strong motivation.

  1. It makes the testsuite a lot slower to run (DMD has to parse entire Phobos for every test)
  2. If this ever fails, it makes it a lot harder to debug into

Thus, there are many "implementations" of tuple in the testsuite (do a grep) - here's a simple one:

struct Tuple(T...){
    T expand;
    alias expand this;
}
auto tuple(T...)(T t){ return Tuple!T(t); }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done; we definitely should add that to CONTRIBUTING.md
also, maybe a linter that would show this kind of policy violation

*/
size_t rem = 0;
for (size_t j = parami + 1; j < nfparams; j++)
{
Parameter p = Parameter.getNth(fparameters, j);
if(p.defaultArg)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@jacob-carlborg
Copy link
Contributor

What happens with code like this, will it compile?

string foo(T...)(T a, string file = __FILE__) if (T.length == 1)
{
    return null;
}
foo(1, "bar");

@timotheecour
Copy link
Contributor Author

timotheecour commented Feb 4, 2018

@jacob-carlborg I added a test. it does not compile as expected (and doesn't get confused by the fact "bar" is of same type as "file").

@timotheecour
Copy link
Contributor Author

timotheecour commented Feb 4, 2018

PTAL, all comments addressed.

You can add at least https://issues.dlang.org/show_bug.cgi?id=8687 to the fixed bugs.

done

This needs a PR to dlang.org to update the spec

will do after this is merged (or do you recommend another way?)

EDIT: see dlang/dlang.org#2169

@WalterBright WalterBright merged commit 793a16e into dlang:master Feb 4, 2018
@jmdavis
Copy link
Member

jmdavis commented Feb 4, 2018

Yay! Thank you, @timotheecour for doing this.

@MartinNowak
Copy link
Member

MartinNowak commented Feb 19, 2018

Just seen it in the changelog, thx.

While working on limited lifetimes (@safe RC/Uniq) I ran into another use-case where a default argument is needed.

Could be helpful would have been the better wording, mainly to pass a default allocator as last argument and keep tracking it's scopeness.

timotheecour added a commit to timotheecour/dlang.org that referenced this pull request Feb 22, 2018
wilzbach pushed a commit to timotheecour/dlang.org that referenced this pull request Feb 23, 2018
timotheecour added a commit to timotheecour/dlang.org that referenced this pull request Feb 23, 2018
timotheecour added a commit to timotheecour/dlang.org that referenced this pull request Feb 23, 2018
@schveiguy
Copy link
Member

schveiguy commented Jul 5, 2018

This PR changed the behavior of valid code. Even though default parameters didn't make sense (because you had to specify them), it didn't stop actual code from using them. See issue 19057.

I think the better fix would be to make default parameters actually work (and match the parameters if the trailing arguments have the same type).

In other words:

void foo(A...)(A a, int x = 1, int y = 2)
{
    writeln(a, x, y);
}

void main()
{
   foo("hello"); // 1
   foo("hello", 5); // 2
   foo("hello", 5, 6); // 3
}

Prior to this PR, 1 and 2 were errors, but 3 worked and printed "hello56"
After this PR, 1, 2, and 3 work, but print "hello12", "hello512", "hello5612" which is a change in behavior for at least one previously working call.

My expectation is that it should print "hello12", "hello52", "hello56". I don't get why parameters with default values should never match the actual arguments.

@jmdavis
Copy link
Member

jmdavis commented Jul 6, 2018

I think the better fix would be to make default parameters actually work (and match the parameters if the trailing arguments have the same type).

That would be a disaster in the making for __FILE__ and __LINE__, because it would then be trivial to accidentally match them and thus give them the wrong values as well as then be missing arguments that were then intended to be part of the variadic arguments. And I really don't see much point in even having non-default parameters after variadic parameters. You can just make them part of the variadic parameters. So, I seriously question that even supporting having non-default parameters after variadic parameters is particularly worth it, and what you're suggesting would make having parameters with default arguments after variadic parameters highly risky (not just for __FILE__ and __LINE__ but for pretty much any default arguments), whereas these changes actually made them work.

@schveiguy
Copy link
Member

That would be a disaster in the making for __FILE__ and __LINE__

No, it wouldn't. You just wouldn't do that, until we can figure out a better way (see issue 18919). Just go back to the templated versions for now.

And I really don't see much point in even having non-default parameters after variadic parameters. You can just make them part of the variadic parameters.

It's pretty obvious to me, to cut down on complexity and template bloat. What's easier/more understandable?

void foo(A...)(A args, int timeout)
// or
void foo(A...)(A args) if (A.length > 0 && is(A[$-1] == int))
{
    int timeout = a[$-1];
}

It's not for us to question how people have written their code. __FILE__ and __LINE__ are not the only use cases for default parameters, and those use cases don't always match the pattern that __FILE__ and __LINE__ do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants