www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Trouble creating a formatted assert wrapper

reply "Peter Alexander" <peter.alexander.au gmail.com> writes:
One thing that's always bothered me is that I have to use 
std.string.format to produce useful assert messages:

assert(x == 1, format("x doesn't equal 1, it is %d", x));


Of course, I tried to solve this by producing my own assert-like 
function wrapper:

void myAssert(Args...)(lazy bool condition, lazy Args args)
{
     assert(condition, format(args));
}


That's all good, but the problem now is that I get the wrong file 
and line in the assert message, so I tried to use the default arg 
__FILE__ and __LINE__ trick:

void myAssert(Args...)(lazy bool condition, lazy Args args, int 
line = __LINE__, string file = __FILE__)
{
     if (!condition)
     {
         writeln("Assertion failed   %s:%d", file, line);
         writefln(args);
         exit(1);
     }
}


But I can't have default arguments because of the variadic 
arguments!

Can anyone think of a way around this?
Sep 02 2012
next sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Sun, 02 Sep 2012 19:14:07 +0200, Peter Alexander  
<peter.alexander.au gmail.com> wrote:

 One thing that's always bothered me is that I have to use  
 std.string.format to produce useful assert messages:

 assert(x == 1, format("x doesn't equal 1, it is %d", x));


 Of course, I tried to solve this by producing my own assert-like  
 function wrapper:

 void myAssert(Args...)(lazy bool condition, lazy Args args)
 {
      assert(condition, format(args));
 }


 That's all good, but the problem now is that I get the wrong file and  
 line in the assert message, so I tried to use the default arg __FILE__  
 and __LINE__ trick:

 void myAssert(Args...)(lazy bool condition, lazy Args args, int line =  
 __LINE__, string file = __FILE__)
 {
      if (!condition)
      {
          writeln("Assertion failed   %s:%d", file, line);
          writefln(args);
          exit(1);
      }
 }


 But I can't have default arguments because of the variadic arguments!

 Can anyone think of a way around this?

void myAssert(int line = __LINE__, string file = __FILE__, Args...)(lazy bool condition, lazy Args args) { if (!condition) { writefln("Assertion failed %s:%d", file, line); writefln(args); exit(1); } } -- Simen
Sep 02 2012
parent reply Nick Treleaven <invalid example.net> writes:
On 02/09/2012 20:31, timotheecour wrote:
 void myAssert(int line = __LINE__, string file = __FILE__,
 Args...)(lazy bool condition, lazy Args args) {



Won't that create "template code bloat" ? Ie everytime myAssert is used a new function is created.

 Another option is to use a lazy tuple argument inside myAssert instead
 of variadic arguments, which allows to pass line and file AFTER, without
 template bloat.

Do you mean: myAssert(condition, tuple("%d", 5)); That might be useful if myAssert was more complex than just wrapping format(), but in this case it doesn't seem much better. Peter Alexander's solution seems good, i.e. using the compile-time default arguments but with the body forwarding to a non-template function: myAssertBody(condition, format("Assertion failed %s:%d: ", file, line, args)); Presumably the compiler can then optimize out each myAssert instantiation.
 A related question:
 In C++ we can stringify arguments and use it to provide informative
 asserts without duplicating code specifying a string version of the
 condition:
 #define assert( isOK ) ( (isOK) ? (void)0 :
 (void)myAssert(#isOK,__LINE__,__PRETTY_FUNCTION__, __FILE__) )

 Likewise for related quick debug functions:
 #define DEBUG(val) myDEBUG(#val,val)

 There seems to be no way of doing this currently (besides the ugly
 mixin(myAssertString("i==0")) which would parse the condition at CT).

Maybe we could have something like: void fun(bool condition, string expr = __STRINGIFY(condition)); fun(i==0); // expr = "i==0"
Sep 03 2012
parent Nick Treleaven <invalid example.net> writes:
On 03/09/2012 13:51, Nick Treleaven wrote:
 myAssertBody(condition, format("Assertion failed   %s:%d: ", file, line,
 args));

Oops, that is unsafe, fixed: void myAssert(string file = __FILE__, int line = __LINE__, Args...)( lazy bool condition, lazy string messageFormat, lazy Args args) if (args.length > 0) { myAssertBody(condition, xformat("Assertion failed %s:%d: %s", file, line, xformat(messageFormat, args))); }
Sep 03 2012
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Sunday, 2 September 2012 at 18:23:48 UTC, Simen Kjaeraas wrote:
 void myAssert(int line = __LINE__, string file = __FILE__, 
 Args...)(lazy bool condition, lazy Args args) {
     if (!condition)
     {
         writefln("Assertion failed   %s:%d", file, line);
         writefln(args);
         exit(1);
     }
 }

Nice. I thought of that, but for some reason I thought that you couldn't have default args before variadic args.
Sep 02 2012
prev sibling next sibling parent "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Sun, 02 Sep 2012 20:46:50 +0200, Peter Alexander  
<peter.alexander.au gmail.com> wrote:

 On Sunday, 2 September 2012 at 18:23:48 UTC, Simen Kjaeraas wrote:
 void myAssert(int line = __LINE__, string file = __FILE__,  
 Args...)(lazy bool condition, lazy Args args) {
     if (!condition)
     {
         writefln("Assertion failed   %s:%d", file, line);
         writefln(args);
         exit(1);
     }
 }

Nice. I thought of that, but for some reason I thought that you couldn't have default args before variadic args.

tbh, I was unsure myself. Sometimes you should just try. :p -- Simen
Sep 02 2012
prev sibling next sibling parent "timotheecour" <thelastmammoth gmail.com> writes:
 void myAssert(int line = __LINE__, string file = __FILE__, 
 Args...)(lazy bool condition, lazy Args args) {



Won't that create "template code bloat" ? Ie everytime myAssert is used a new function is created. There was if I remember some discussion regarding not passing __LINE__,__FILE__ in assert related functions because of that. If backtrace worked properly we should be able to go up the stack and get line number information when the assert fails (no time penalty when assert doesn't fail). Another option is to use a lazy tuple argument inside myAssert instead of variadic arguments, which allows to pass line and file AFTER, without template bloat. A related question: In C++ we can stringify arguments and use it to provide informative asserts without duplicating code specifying a string version of the condition: #define assert( isOK ) ( (isOK) ? (void)0 : (void)myAssert(#isOK,__LINE__,__PRETTY_FUNCTION__, __FILE__) ) Likewise for related quick debug functions: #define DEBUG(val) myDEBUG(#val,val) There seems to be no way of doing this currently (besides the ugly mixin(myAssertString("i==0")) which would parse the condition at CT). Would that be possible to add?
Sep 02 2012
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Sun, Sep 2, 2012 at 8:46 PM, Peter Alexander
<peter.alexander.au gmail.com> wrote:
 On Sunday, 2 September 2012 at 18:23:48 UTC, Simen Kjaeraas wrote:
 void myAssert(int line = __LINE__, string file = __FILE__, Args...)(lazy
 bool condition, lazy Args args) {
     if (!condition)
     {
         writefln("Assertion failed   %s:%d", file, line);
         writefln(args);
         exit(1);
     }
 }

Nice. I thought of that, but for some reason I thought that you couldn't have default args before variadic args.

IIRC, you can even have them before standard template args. Template arguments are much more versatile (or hard-working) than function arguments.
Sep 02 2012
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Sunday, 2 September 2012 at 19:31:24 UTC, timotheecour wrote:
 void myAssert(int line = __LINE__, string file = __FILE__, 
 Args...)(lazy bool condition, lazy Args args) {



Won't that create "template code bloat" ? Ie everytime myAssert is used a new function is created.

You can easily counter that by delegating the body to a non-template function. Even then, it's only in non-release builds and it's a small function anyway.
Sep 02 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09/02/2012 07:14 PM, Peter Alexander wrote:
 One thing that's always bothered me is that I have to use
 std.string.format to produce useful assert messages:

 assert(x == 1, format("x doesn't equal 1, it is %d", x));


 Of course, I tried to solve this by producing my own assert-like
 function wrapper:

 void myAssert(Args...)(lazy bool condition, lazy Args args)
 {
      assert(condition, format(args));
 }


 That's all good, but the problem now is that I get the wrong file and
 line in the assert message, so I tried to use the default arg __FILE__
 and __LINE__ trick:

 void myAssert(Args...)(lazy bool condition, lazy Args args, int line =
 __LINE__, string file = __FILE__)
 {
      if (!condition)
      {
          writeln("Assertion failed   %s:%d", file, line);
          writefln(args);
          exit(1);
      }
 }


 But I can't have default arguments because of the variadic arguments!

 Can anyone think of a way around this?

I don't think there is a deep reason why IFTI shouldn't succeed in this case.
Sep 02 2012
parent reply Don Clugston <dac nospam.com> writes:
On 03/09/12 23:48, Chris Nicholson-Sauls wrote:
 On Monday, 3 September 2012 at 11:17:39 UTC, Chris Nicholson-Sauls wrote:
 1) Empty array stands in for empty variadic. [snip]

 In reality, though, we have neither of these things. [snip]

Turns out, I was quite wrong, and I'm happy to be. Empty array is accepted for typesafe variadics just fine ... I'm not sure now why I thought otherwise. Of course, our tangent is really sort of moot, since the brackets would be required for any pattern with a suffix matching the optional arguments anyhow, at which point there's little point in having the variadic argument. I reiterate my impression that magical __FILE__ and __LINE__ should be provided by some other means.

It was a special-case hack to fix a special-case need. It was extremely easy to implement (about 2 hours work) and has been very successful in fixing that need. Everything else that anyone has talked about is at least ten times as complicated, and doesn't seem to offer significant advantages. It's really easy to come up with over-engineered solutions to these sorts of things.
Sep 04 2012
parent Don Clugston <dac nospam.com> writes:
On 04/09/12 11:43, Chris Nicholson-Sauls wrote:
 On Tuesday, 4 September 2012 at 09:24:26 UTC, Don Clugston wrote:
 On 03/09/12 23:48, Chris Nicholson-Sauls wrote:
 I reiterate my impression that magical __FILE__ and
 __LINE__ should be provided by some other means.

It was a special-case hack to fix a special-case need. It was extremely easy to implement (about 2 hours work) and has been very successful in fixing that need. Everything else that anyone has talked about is at least ten times as complicated, and doesn't seem to offer significant advantages. It's really easy to come up with over-engineered solutions to these sorts of things.

How difficult would hidden params, triggered by usage, be? Ie: my function makes use of __CALL_FILE and __CALL_LINE variables by name, therefore they are tacked on (as const, presumably) and always passed, thanks to diabolic compiler sorcery. The current solution is fine in a majority of cases, at least so far, but there are going to be moments like what the OP had, and these moments are discouraging especially to newcomers.

I don't know how that could be done. You need to know the function signature whenever you call the function. If you make the signature dependent on the function body, it cannot work unless you have the source code of the function. The compiler is able to add hidden variables for things like 'this' because it can work out if it is required just by looking at the types involved in the function declaration. But in this case, it can't get it from the signature. The default argument method we are currently using is a nice trick, because even though default arguments aren't part of the function type, the compiler still sees them in the function signature, so it knows to do a bit of magic. It's also easy to get nasty forward reference errors when you do that sort of thing.
 I won't pretend to know if it would be easy or not; you're a heck of a
 lot more familiar with the compiler's code than I am.  But it certainly
 seems straightforward.

Sep 04 2012
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Sunday, 2 September 2012 at 20:30:26 UTC, Timon Gehr wrote:
 On 09/02/2012 07:14 PM, Peter Alexander wrote:
 One thing that's always bothered me is that I have to use
 std.string.format to produce useful assert messages:

 assert(x == 1, format("x doesn't equal 1, it is %d", x));


 Of course, I tried to solve this by producing my own 
 assert-like
 function wrapper:

 void myAssert(Args...)(lazy bool condition, lazy Args args)
 {
     assert(condition, format(args));
 }


 That's all good, but the problem now is that I get the wrong 
 file and
 line in the assert message, so I tried to use the default arg 
 __FILE__
 and __LINE__ trick:

 void myAssert(Args...)(lazy bool condition, lazy Args args, 
 int line =
 __LINE__, string file = __FILE__)
 {
     if (!condition)
     {
         writeln("Assertion failed   %s:%d", file, line);
         writefln(args);
         exit(1);
     }
 }


 But I can't have default arguments because of the variadic 
 arguments!

 Can anyone think of a way around this?

I don't think there is a deep reason why IFTI shouldn't succeed in this case.

Consider: myAssert(false, "%d", 1); What is Args? "%d", 1 could refer to the optional arguments, or the variadic arguments. Both match.
Sep 02 2012
prev sibling next sibling parent "Chris Nicholson-Sauls" <ibisbasenji gmail.com> writes:
On Sunday, 2 September 2012 at 23:40:01 UTC, Peter Alexander 
wrote:
 Consider:

 myAssert(false, "%d", 1);

 What is Args? "%d", 1 could refer to the optional arguments, or 
 the variadic arguments. Both match.

Easy: the variadic arguments are not, themselves, optional. Match the variadic. This kind of problem is already solved in every language with "scattering" assignments, and other such features.
Sep 03 2012
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Monday, 3 September 2012 at 09:15:08 UTC, Chris 
Nicholson-Sauls wrote:
 On Sunday, 2 September 2012 at 23:40:01 UTC, Peter Alexander 
 wrote:
 Consider:

 myAssert(false, "%d", 1);

 What is Args? "%d", 1 could refer to the optional arguments, 
 or the variadic arguments. Both match.

Easy: the variadic arguments are not, themselves, optional. Match the variadic. This kind of problem is already solved in every language with "scattering" assignments, and other such features.

The variadic arguments are optional, as you can have zero arguments count as variadic. Also, how can I override the optional arguments in this function if all arguments are matched as variadic?
Sep 03 2012
prev sibling next sibling parent "Chris Nicholson-Sauls" <ibisbasenji gmail.com> writes:
On Monday, 3 September 2012 at 09:24:42 UTC, Peter Alexander 
wrote:
 On Monday, 3 September 2012 at 09:15:08 UTC, Chris 
 Nicholson-Sauls wrote:
 On Sunday, 2 September 2012 at 23:40:01 UTC, Peter Alexander 
 wrote:
 Consider:

 myAssert(false, "%d", 1);

 What is Args? "%d", 1 could refer to the optional arguments, 
 or the variadic arguments. Both match.

Easy: the variadic arguments are not, themselves, optional. Match the variadic. This kind of problem is already solved in every language with "scattering" assignments, and other such features.

The variadic arguments are optional, as you can have zero arguments count as variadic. Also, how can I override the optional arguments in this function if all arguments are matched as variadic?

They are not really optional. That the language fails to enforce this (or provide an eforcement, rather) is a shortcoming. Pretending for a moment that D supports one of two things: 1) Empty array stands in for empty variadic. Then, given arguments like func(R, V..., O=D) you would call it with func(foo, [], bar). Since a typesafe variadic is effectively a sugar-laden array (well a tuple really), I see no reason this shouldn't be feasible. First-class tuples would likely be even better. 2) Named parameters. Then it's func(foo, O: bar), and the variadic argument can indeed be optional, although the meaning is no different than in (1). In reality, though, we have neither of these things. Even so, how often does this actually arise in practice (never has for me)? And given the OP's specific case (__FILE__ and __LINE__ params) I don't foresee any useful case for overriding the defaults. Honestly, I sometimes think that the special treatment of __FILE__ and __LINE__ when used as defaults was not the best way to go. It might've been better implemented as hidden params, only generated by the compiler when actually used in the body.
Sep 03 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, September 03, 2012 11:25:05 Peter Alexander wrote:
 On Monday, 3 September 2012 at 09:15:08 UTC, Chris
 
 Nicholson-Sauls wrote:
 On Sunday, 2 September 2012 at 23:40:01 UTC, Peter Alexander
 
 wrote:
 Consider:
 
 myAssert(false, "%d", 1);
 
 What is Args? "%d", 1 could refer to the optional arguments,
 or the variadic arguments. Both match.

Easy: the variadic arguments are not, themselves, optional. Match the variadic. This kind of problem is already solved in every language with "scattering" assignments, and other such features.

The variadic arguments are optional, as you can have zero arguments count as variadic.

You can use a template constraint if you want to require that there be at least a certain number of arguments. Also, if you know that there always needs to be at least a certain number of arguments, then you can simply make it so that the required ones are non-variadic. e.g. void func(A, B, C...)(A a, B b, C cs) { ... }
 Also, how can I override the optional arguments in this function
 if all arguments are matched as variadic?

Use template constraints. - Jonathan M Davis
Sep 03 2012
prev sibling next sibling parent "Chris Nicholson-Sauls" <ibisbasenji gmail.com> writes:
On Monday, 3 September 2012 at 11:17:39 UTC, Chris 
Nicholson-Sauls wrote:
 1) Empty array stands in for empty variadic. [snip]

 In reality, though, we have neither of these things. [snip]

Turns out, I was quite wrong, and I'm happy to be. Empty array is accepted for typesafe variadics just fine ... I'm not sure now why I thought otherwise. Of course, our tangent is really sort of moot, since the brackets would be required for any pattern with a suffix matching the optional arguments anyhow, at which point there's little point in having the variadic argument. I reiterate my impression that magical __FILE__ and __LINE__ should be provided by some other means.
Sep 03 2012
prev sibling next sibling parent "Chris Nicholson-Sauls" <ibisbasenji gmail.com> writes:
On Tuesday, 4 September 2012 at 09:24:26 UTC, Don Clugston wrote:
 On 03/09/12 23:48, Chris Nicholson-Sauls wrote:
 I reiterate my impression that magical __FILE__ and
 __LINE__ should be provided by some other means.

It was a special-case hack to fix a special-case need. It was extremely easy to implement (about 2 hours work) and has been very successful in fixing that need. Everything else that anyone has talked about is at least ten times as complicated, and doesn't seem to offer significant advantages. It's really easy to come up with over-engineered solutions to these sorts of things.

How difficult would hidden params, triggered by usage, be? Ie: my function makes use of __CALL_FILE and __CALL_LINE variables by name, therefore they are tacked on (as const, presumably) and always passed, thanks to diabolic compiler sorcery. The current solution is fine in a majority of cases, at least so far, but there are going to be moments like what the OP had, and these moments are discouraging especially to newcomers. I won't pretend to know if it would be easy or not; you're a heck of a lot more familiar with the compiler's code than I am. But it certainly seems straightforward.
Sep 04 2012
prev sibling parent "Chris Nicholson-Sauls" <ibisbasenji gmail.com> writes:
On Tuesday, 4 September 2012 at 11:28:23 UTC, Don Clugston wrote:
 I don't know how that could be done. You need to know the 
 function signature whenever you call the function. If you make 
 the signature dependent on the function body, it cannot work 
 unless you have the source code of the function. The compiler 
 is able to add hidden variables for things like 'this' because 
 it can work out if it is required just by looking at the types 
 involved in the function declaration. But in this case, it 
 can't get it from the signature.

 The default argument method we are currently using is a nice 
 trick, because even though default arguments aren't part of the 
 function type, the compiler still sees them in the function 
 signature, so it knows to do a bit of magic.

 It's also easy to get nasty forward reference errors when you 
 do that sort of thing.

Fair enough; I hadn't considered the case of having only the signature to go by. Maybe an attribute for it? ;) Okay okay, I give. For now there's making file/line template params, which is fine for something like a custom assertion meant for debug builds, although horrid for release builds.
Sep 05 2012