Limitations and workarounds to expressions defining size of an array

F

Francois Grieu

In C99:
6.7.5.2p1: "... the expression shall have an integer type. If the
expression is a constant expression, it shall have a value greater than
zero."
6.7.5.2p2: "... If an identifier is declared to be an object with static
storage duration, it shall not have a variable length array type."
6.7.5.2p4: "... If the size is an integer constant expression and the
element type has a known constant size, the array type is not a variable
length array type; otherwise, the array type is a variable length array
type."
6.7.5.2p5: "If the size is an expression that is not an integer constant
expression expression: if it occurs in a declaration at function
prototype scope, it is treated as if it were replaced by *; otherwise,
each time it is evaluated it shall have a value greater than zero."

Yes; also relevant is 6.6p6
An integer constant expression (.. used to specify .. the size of an
array ..) shall have integer type and shall only have operands
that are integer constants, enumeration constants, character
constants, sizeof expressions whose results are integer constants,
and floating constants that are the immediate operands of casts.
Cast operators in an integer constant expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof operator.

I read that as C99 NOT allowing (except in automatic context, as a
VLA) even basic use of floating point as in

struct foo * bar[(unsigned)(1000*2.718)];

Whether or not that's permitted depends upon precisely what you mean
by that phrase - it's not clear from context.

I was hoping for (not tested)

// select temporary type
#include <limits.h>
#ifdef ULLONG_MAX
#define UTMP static const unsigned long long
#else
#define UTMP static const unsigned long
#endif

#define EXPECTED 15000
UTMP kExpected = EXPECTED;
UTMP kRoot2 = 12*12*Expected;
// we want ceil(sqrt(12*12*Expected)), use Newton/Rapson
UTMP kExTmp0 = (uwl)3<<(kRoot2>>30 > 0 ? 15 :
kRoot2>>24 > 0 ? 12 :
kRoot2>>18 > 0 ? 9 :
kRoot2>>12 > 0 ? 6 : 3);
UTMP kExTmp1 = (kExTmp0+kRoot2/kExTmp0)/2;
UTMP kExTmp2 = (kExTmp1+kRoot2/kExTmp1)/2;
UTMP kExTmp3 = (kExTmp2+kRoot2/kExTmp2)/2;
UTMP kExTmp4 = (kExTmp3+kRoot2/kExTmp3)/2;
UTMP kExTmp5 = (kExTmp4+kRoot2/kExTmp4)/2;
UTMP kExTmp6 = (kExTmp5+kRoot2/kExTmp5)/2;
UTMP kExTmp7 = (kExTmp6+kRoot2/kExTmp6)/2;
UTMP kExTmp8 = (kExTmp7+kRoot2/kExTmp7)/2;
UTMP kExTmp9 = (kExTmp8+kRoot2/kExTmp8)/2;
UTMP kExTmpA = (kExTmp9+kRoot2/kExTmp9)/2;
UTMP kExTmpB = (kExTmpA+kRoot2/kExTmpA)/2;
UTMP kExTmpC = (kExTmpB+kRoot2/kExTmpB)/2;
UTMP kRoot = kExTmpC+(kExTmpB>kExTmpC?1:0);

struct foo * bar[kExpected+kRoot];

Is that valid in C11?

Francois Grieu
 
F

Francois Grieu

In C99:
6.7.5.2p1: "... the expression shall have an integer type. If the
expression is a constant expression, it shall have a value greater than
zero."
6.7.5.2p2: "... If an identifier is declared to be an object with static
storage duration, it shall not have a variable length array type."
6.7.5.2p4: "... If the size is an integer constant expression and the
element type has a known constant size, the array type is not a variable
length array type; otherwise, the array type is a variable length array
type."
6.7.5.2p5: "If the size is an expression that is not an integer constant
expression expression: if it occurs in a declaration at function
prototype scope, it is treated as if it were replaced by *; otherwise,
each time it is evaluated it shall have a value greater than zero."

Yes; also relevant is 6.6p6
An integer constant expression (.. used to specify .. the size of an
array ..) shall have integer type and shall only have operands
that are integer constants, enumeration constants, character
constants, sizeof expressions whose results are integer constants,
and floating constants that are the immediate operands of casts.
Cast operators in an integer constant expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof operator.

I read that as C99 NOT allowing (except in automatic context, as a
VLA) even basic use of floating point as in

struct foo * bar[(unsigned)(1000*2.718)];

Whether or not that's permitted depends upon precisely what you mean
by that phrase - it's not clear from context.

I was hoping for
// select temporary type
#include <limits.h>
#ifdef ULLONG_MAX
typedef unsigned long long uw;
#else
typedef unsigned long uw;
#endif
#define UTMP static const uw

#define EXPECTED 15000
UTMP kExpected = EXPECTED;
UTMP kRoot2 = 12*12*kExpected;
// we want ceil(sqrt(12*12*kExpected)), use Newton/Rapson
UTMP kExTmp0 = (uw)3<<(kRoot2>>30 > 0 ? 15 :
kRoot2>>24 > 0 ? 12 :
kRoot2>>18 > 0 ? 9 :
kRoot2>>12 > 0 ? 6 : 3);
UTMP kExTmp1 = (kExTmp0+kRoot2/kExTmp0)/2;
UTMP kExTmp2 = (kExTmp1+kRoot2/kExTmp1)/2;
UTMP kExTmp3 = (kExTmp2+kRoot2/kExTmp2)/2;
UTMP kExTmp4 = (kExTmp3+kRoot2/kExTmp3)/2;
UTMP kExTmp5 = (kExTmp4+kRoot2/kExTmp4)/2;
UTMP kExTmp6 = (kExTmp5+kRoot2/kExTmp5)/2;
UTMP kExTmp7 = (kExTmp6+kRoot2/kExTmp6)/2;
UTMP kExTmp8 = (kExTmp7+kRoot2/kExTmp7)/2;
UTMP kExTmp9 = (kExTmp8+kRoot2/kExTmp8)/2;
UTMP kExTmpA = (kExTmp9+kRoot2/kExTmp9)/2;
UTMP kExTmpB = (kExTmpA+kRoot2/kExTmpA)/2;
UTMP kExTmpC = (kExTmpB+kRoot2/kExTmpB)/2;
UTMP kRoot = kExTmpC+(kExTmpB>kExTmpC?1:0);

struct foo * bar[kExpected+kRoot];


This is not valid C99 (the definition of kRoot2 is invalid);
is that valid in C11? <ot>Note: it seem valid in C++</ot>.

Francois Grieu
 
F

Francois Grieu

The Standard draws a line and says "All C compilers accept
everything on this side of the line as constants." But the line
is permeable: "An implementation may accept other forms of constant
expressions" (6.6p10). The upshot:

1) A C compiler must accept `static int x[2+1];', because
`2+1' is on the hither side of the line,

2) A C compiler may reject `static int x[strlen("abc")];'
because `strlen("abc")' is on the thither side, but

3) A C compiler may accept `static int x[strlen("abc")];'
under the "other forms" allowance, and need not even
issue a diagnostic.

Cases (2) and (3) illustrate why "It works with my compiler"
does not define the language.

Well said.

Unless I err, we have a portable idiom equivalent to that:

static int x[sizeof("abc")-1];


Francois Grieu
 
J

James Kuyper

On 09/04/2012 01:14 PM, Francois Grieu wrote:
....
Yes; also relevant is 6.6p6
An integer constant expression (.. used to specify .. the size of an
array ..) shall have integer type and shall only have operands
that are integer constants, enumeration constants, character
constants, sizeof expressions whose results are integer constants,
and floating constants that are the immediate operands of casts.
Cast operators in an integer constant expression shall only
convert arithmetic types to integer types, except as part of an
operand to the sizeof operator.

I read that as C99 NOT allowing (except in automatic context, as a
VLA) even basic use of floating point as in

struct foo * bar[(unsigned)(1000*2.718)];

That is correct. Which is why, if you want to do this, it will have to
be an array with automatic storage duration. Does it need to have static
storage duration?

....
I was hoping for (not tested)

// select temporary type
#include <limits.h>
#ifdef ULLONG_MAX
#define UTMP static const unsigned long long
#else
#define UTMP static const unsigned long
#endif

I'd recommend using a typedef rather than #define for that purpose. If
you do use a typedef, you should also change the name, since
fully-capitalized names are, by convention, used mainly for macros.
#define EXPECTED 15000
UTMP kExpected = EXPECTED;
UTMP kRoot2 = 12*12*Expected;
// we want ceil(sqrt(12*12*Expected)), use Newton/Rapson
UTMP kExTmp0 = (uwl)3<<(kRoot2>>30 > 0 ? 15 :
kRoot2>>24 > 0 ? 12 :
kRoot2>>18 > 0 ? 9 :
kRoot2>>12 > 0 ? 6 : 3);
UTMP kExTmp1 = (kExTmp0+kRoot2/kExTmp0)/2;
UTMP kExTmp2 = (kExTmp1+kRoot2/kExTmp1)/2;
UTMP kExTmp3 = (kExTmp2+kRoot2/kExTmp2)/2;
UTMP kExTmp4 = (kExTmp3+kRoot2/kExTmp3)/2;
UTMP kExTmp5 = (kExTmp4+kRoot2/kExTmp4)/2;
UTMP kExTmp6 = (kExTmp5+kRoot2/kExTmp5)/2;
UTMP kExTmp7 = (kExTmp6+kRoot2/kExTmp6)/2;
UTMP kExTmp8 = (kExTmp7+kRoot2/kExTmp7)/2;
UTMP kExTmp9 = (kExTmp8+kRoot2/kExTmp8)/2;
UTMP kExTmpA = (kExTmp9+kRoot2/kExTmp9)/2;
UTMP kExTmpB = (kExTmpA+kRoot2/kExTmpA)/2;
UTMP kExTmpC = (kExTmpB+kRoot2/kExTmpB)/2;
UTMP kRoot = kExTmpC+(kExTmpB>kExTmpC?1:0);

struct foo * bar[kExpected+kRoot];

Is that valid in C11?

I don't see any problems. Did you have any particular reason for
doubting that it was valid? If so, you might be right - I'm far less
reliable when I say "I see no problems" than when I say "I see a problem".
 
J

James Kuyper

On 09/05/2012 03:56 AM, Francois Grieu wrote:
....
I was hoping for
// select temporary type
#include <limits.h>
#ifdef ULLONG_MAX
typedef unsigned long long uw;
#else
typedef unsigned long uw;
#endif
#define UTMP static const uw

#define EXPECTED 15000
UTMP kExpected = EXPECTED;
UTMP kRoot2 = 12*12*kExpected;
// we want ceil(sqrt(12*12*kExpected)), use Newton/Rapson
UTMP kExTmp0 = (uw)3<<(kRoot2>>30 > 0 ? 15 :
kRoot2>>24 > 0 ? 12 :
kRoot2>>18 > 0 ? 9 :
kRoot2>>12 > 0 ? 6 : 3);
UTMP kExTmp1 = (kExTmp0+kRoot2/kExTmp0)/2;
UTMP kExTmp2 = (kExTmp1+kRoot2/kExTmp1)/2;
UTMP kExTmp3 = (kExTmp2+kRoot2/kExTmp2)/2;
UTMP kExTmp4 = (kExTmp3+kRoot2/kExTmp3)/2;
UTMP kExTmp5 = (kExTmp4+kRoot2/kExTmp4)/2;
UTMP kExTmp6 = (kExTmp5+kRoot2/kExTmp5)/2;
UTMP kExTmp7 = (kExTmp6+kRoot2/kExTmp6)/2;
UTMP kExTmp8 = (kExTmp7+kRoot2/kExTmp7)/2;
UTMP kExTmp9 = (kExTmp8+kRoot2/kExTmp8)/2;
UTMP kExTmpA = (kExTmp9+kRoot2/kExTmp9)/2;
UTMP kExTmpB = (kExTmpA+kRoot2/kExTmpA)/2;
UTMP kExTmpC = (kExTmpB+kRoot2/kExTmpB)/2;
UTMP kRoot = kExTmpC+(kExTmpB>kExTmpC?1:0);

struct foo * bar[kExpected+kRoot];


This is not valid C99 (the definition of kRoot2 is invalid);
is that valid in C11? <ot>Note: it seem valid in C++</ot>.

Ah - I see the point you're raising now. Sorry for missing it in your
previous message - I didn't notice your use of 'static' in the
#definition of UTMP. Initializers for an object of static storage
duration must be constant expressions. In C++, a const variable
initialized with a constant expression can itself be used as a constant
expression, but in C it is not allowed. You'd have to re-write this as a
single constant expression, and you'll have to be careful to avoid
running into line length limitations if you do so. I haven't checked to
be sure, but you might have settle for fewer iterations.

If you remove the 'static' and define these inside a function, there
should be no problem except with the definition of bar itself, which
requires VLA support. Of course, if you can use a VLA, you wouldn't need
to use such a round-about method to do this, you could simply calculate
the sqrt() directly.
 
A

army1987

The only printf() calls explicitly mentioned in this thread so far were
in my example program, which used "%zu", not "%d", so you're presumably
referring to some other program. What does the source code look like?

#include <stdio.h>
int main(void)
{
printf("%d\n", (int)strspn("BartC", "abcdefghijklmnopqrsBCuvwxyz"));
return 0;
}
 
J

James Kuyper

#include <stdio.h>
int main(void)
{
printf("%d\n", (int)strspn("BartC", "abcdefghijklmnopqrsBCuvwxyz"));
return 0;
}

OK, that's roughly what I expected, but I wanted to be sure. In
particular, your "WTF?" left me wondering whether you thought the
optimization was invalid. I'm mildly impressed by that optimization, but
not nearly as much as you were, apparently.
 
A

army1987

In
particular, your "WTF?" left me wondering whether you thought the
optimization was invalid.

I don't. But 1) I was surprised that strspn() was optimized away, and 2)
I seemed to remember seeing a compiler optimizing away printf() some time
ago, so I expected that if a compiler would do the former it would do the
latter too.
 
F

Francois Grieu

(..) Initializers for an object of static storage
duration must be constant expressions. In C++, a const variable
initialized with a constant expression can itself be used as a constant
expression, but in C it is not allowed. You'd have to re-write this as a
single constant expression, and you'll have to be careful to avoid
running into line length limitations if you do so. I haven't checked to
be sure, but you might have settle for fewer iterations.

I think that I found a conforming workaround: use enums instead
of constants. There is the limitation that enums can only be
trusted to hold 15 bits, but I can workaround that too using
the following ZS and ZG macros.


// select temporary type uw
#include <limits.h>
#ifdef ULLONG_MAX
typedef unsigned long long uw;
#else
typedef unsigned long uw;
#endif

// Utility macro to set symbol s to value v (60-bit capacity)
#define ZS(s,v) enum{e0##s=(v)&32767,e1##s=(v)>>15&32767,\
e2##s=(v)>>15>>15&32767,e3##s=(v)>>15>>15>>15&32767}

// Utility macro to get value of symbol s (60-bit capacity)
#define ZG(s) (((((uw)e3##s<<15|e2##s)<<15|e1##s))<<15|e0##s)

// Set of macros to compute floor(sqrt(x)) for x>0.
// Find a raw approximation of sqrt(x)
#define SQRT0(x) ((uw)8<<(3*((x)<512?0:(x)>>15<1?1:(x)>>15<64?2:\
(x)>>15>>9<8?3:(x)>>15>>15<8?4:(x)>>15>>15>>9<1?5:\
(x)>>15>>15>>15<1?6:(x)>>15>>15>>15<64?7:(x)>>15>>15>>15>>9<8?8:9)))
// One Newton-Raphson refinement step
#define SQRT1(x,a) (((x)/(a)+(a))/2)
// Four Newton-Raphson refinement steps
#define SQRT4(x,a) (SQRT1(x,SQRT1(x,SQRT1(x,SQRT1(x,a)))))
// Final tweak to get exact value
#define SQRTF(x,a) (SQRT1(x,a)-(SQRT1(x,a)>(a)?1:0))

#define EXPECTED 400000000

// Now we want:
// struct foo * bar[EXPECTED+ceil(12*sqrt(EXPECTED)];
// that is
// struct foo * bar[EXPECTED+1+floor(sqrt(12*12*EXPECTED-1)];
#define KVALUE (12*12*(uw)(EXPECTED)-1)
ZS(kExTmp0, SQRT0(KVALUE)); // first approximation
ZS(kExTmp1, SQRT4(KVALUE,ZG(kExTmp0))); // improve..
ZS(kExTmp2, SQRT4(KVALUE,ZG(kExTmp1))); // improve..
ZS(kExTmp3, SQRT4(KVALUE,ZG(kExTmp2))); // improve..
#define KROOT SQRTF(KVALUE,ZG(kExTmp3)) // finalize
struct foo * bar[EXPECTED+1+KROOT];


This works and (as far as I can tell) is conformant, but many
will understandably frown on it as hairy. Especially if we push
it further and implement floating-point-as-enum.


Questions: In C11, what liberties do we have?
In particular,
- can we define the size of a static array (non-VLA)
using a static const long ?
- can we use a static const long freely in an
expression that defines another static const long ?
- can we use floating point arithmetic in expressions defining
a static const long ?
- can we use exp() and log() in such expressions ?

Francois Grieu
 
B

Ben Bacarisse

army1987 said:
I don't. But 1) I was surprised that strspn() was optimized away, and 2)
I seemed to remember seeing a compiler optimizing away printf() some time
ago, so I expected that if a compiler would do the former it would do the
latter too.

gcc will replace some printf calls that use %s with calls to simpler
functions like puts, but replacing a call that uses a %d format is at
least two steps: convert the constant integer to a string and then
replace the printf call. I suspect the conversion puts too great a
distance between what's written and the improved result.

An implementation could choose to replace printf calls that is %d with
other functions designed to printf numbers directly, but that would mean
introducing special functions for this relatively rare optimisation.
Had C already had print_int and so on I imagine it might have been
considered (is that hedged enough!?).
 
E

Eric Sosman

[...]
Questions: In C11, what liberties do we have?
In particular,
- can we define the size of a static array (non-VLA)
using a static const long ?

Sure:

static const long scl = 42;
static int array[sizeof scl];

But for the question I think you're really asking, the
answer is No. First read 6.7.6.2p4 to learn that an array
with a dimension that is not an integer constant expression
is a VLA. Then read 6.6p6 to find out how an ICE is formed;
note that `static const long' does not match any of the allowed
operands. Hence an expression using the value of an SCL is
not an ICE, and an array declared with such an expression as
its dimension is a VLA.
- can we use a static const long freely in an
expression that defines another static const long ?

No. 6.6p7-9.
- can we use floating point arithmetic in expressions defining
a static const long ?

Yes. 6.6p8.
- can we use exp() and log() in such expressions ?

No. 6.6p6-9.

6.6p10 offers a loophole: "An implementation may accept
other forms of constant expressions," so for any particular
compiler some of the Noes may turn to Yesses. But you wrote
"freely," which I'm interpreting as "works on all C11 compilers,"
and with that understanding the Noes have it.
 
E

Eric Sosman

I don't. But 1) I was surprised that strspn() was optimized away, and 2)
I seemed to remember seeing a compiler optimizing away printf() some time
ago, so I expected that if a compiler would do the former it would do the
latter too.

Pre-computation of strspn() surprised me, too. But compilers
have been handling certain special cases of printf() for a long
time. More than fifteen years ago, a colleague told me of writing
the transformation

printf("%s\n", ptr); /* as written */
to
puts(ptr); /* as compiled */

.... with the usual justification: "One of the SPEC benchmarks used
the printf() a lot, and we got a speed increase by eliminating the
format interpretation." <Checks> A fairly elderly gcc does this,
even at the default optimization level.
 
J

James Kuyper

I don't. But 1) I was surprised that strspn() was optimized away, and 2)
I seemed to remember seeing a compiler optimizing away printf() some time
ago, so I expected that if a compiler would do the former it would do the
latter too.

What this particular format string causes printf() to do is relatively
simple. The whole thing could have been optimized all the way down to
fputs("3", stdout). However, in general printf() is far more complicated
than strspn(), which might explain why the strspn() call got evaluated
at compile time, while the printf() call did not.

In particular, printf()'s behavior depends upon the current locale,
while strspn()'s does not. In this simple program, it's easy to confirm
that there's no possibility of setlocale() being called. However, the
need to check for that possibility might have discouraged any attempt to
pre-evaluate the printf() call.
 
T

Tim Rentsch

Francois Grieu said:
I'm trying to figure out the exact limitations that (possibly:
various versions of ISO/IEC 9899) C puts to expressions defining
the size of a plain array at compile time, and how to workaround
that. [.. rearranging ..] Can I
- use floating point at all ?

Not portably.
- if yes: use sqrt, log, exp ?
No.

- or at least, use intermediate values (other than
macro identifiers) to clarify things and remain below
the "4095 characters in a logical source line" limit?

Note that "logical source lines" occur in translation phase 2,
whereas macro expansion is done in phase 4 (and is done in terms
of tokens, not characters). The 4095 character limit affects
macro definitions, not macro expansions.
I can think of enum as intermediates, [snip] but they have
rather silly range limitations.

Yes, generally expressions using macro expansion are better
than using enum constants.
An application would be, given EXPECTED, to define at compile
time an array with EXPECTED + 12*sqrt(EXPECTED) elements, or
some even more complex formula, [snip example]

I think the above works for any EXPECTED>=900 up to
some large value like ULONG_MAX-ULONG_MAX/19 and then some,
and errs on the safe side, with at worse 5% wasted memory;
but this is
- unclear at best;
- painful to extend to lower bounds or less wasted memory;
- hard to generalize to more complex formula involving
for example log or exp.

If you need to do something like this, generally a good way of
approaching it is to divide the input space up into ranges and
find a good integer approximation in each range. There can be
interplay between the ranges and the approximations chosen. For
example, using ceil( 12 * sqrt( x ) ) as the desired target (and
must never be less than that, or less than one), the target
function could be approximated using

#define SQ12(n) SQ12_((n))
#define SQ12_(n) ( \
n < 2 ? 12*n + (n==0) : \
n < 3 ? 17 : \
n < 4 ? 21 : \
n < 16 ? SQx(12*n/8,144*n) : \
n < 1055 ? SQx(12*n/13,144*n) : \
n < 26866 ? SQx(12*n/80,144*n) : \
n < 361166 ? SQx(12*n/331,144*n) : \
n < 3321166 ? SQx(12*n/1093,144*n): \
n < 22000000 ? SQx(12*n/2992,144*n) : \
n < 160000000 ? 12 * SQx(n/6543,n) : \
n < 1200000000 ? 12 * SQx(n/12500,n) : \
12 * SQx(n/45000,n) \
)

#define SQx( k, n ) SQx_((k),(n))
#define SQx_( k, n ) (1+SQ3_(k,n))
#define SQ3_( k, n ) SQ2_(SQ1_(k,n),n)
#define SQ2_( k, n ) SQ1_(SQ1_(k,n),n)
#define SQ1_( k, n ) ((k+n/k)/2)

#define EXPECTED 6999999

int HERE = SQ12( EXPECTED );

Obviously the ranges and specific constants were chosen by
experimentation. Naturally there is a tradeoff between the
complexity involved and the accuracy desired (and also what
input domain needs to be covered). The particular definition
above works over all 32 bit inputs, and has good accuracy (off
by at most one up to n = 20000000, and within a factor of
1.005 above that). Of course it is possible to find simpler
formulations or more accurate ones; the point is to pick an
approximation that suits the needs of the problem to be solved
-- dividing into ranges and using various approximations in
the various ranges is general and flexible, understandable,
and also fairly cheap in terms of expanded token count.

Incidentally, the sample code above generates an expansion
(the line with HERE in it) of about 3000 characters.

(Ben's suggestion is also good. For anyone interested I think
it's worth looking at the similarities and differences of the
two approaches.)
 
T

Tim Rentsch

Eric Sosman said:
Yes, I'm sure that's a common optimization.

But the phrase "constant expression" is defined by the standard in
terms of what it can contain; it doesn't merely mean "an expression
that a sufficiently clever compiler can evaluate at compile time".


But where do you draw the line between constant and non-constant
expressions? The authors of the standard already decided exactly
where to draw that line. Moving it in the direction of treating
more expressions as constant risks imposing requirements that some
compilers may not be able to meet.

The Standard draws a line and says "All C compilers accept
everything on this side of the line as constants." But the line
is permeable: "An implementation may accept other forms of constant
expressions" (6.6p10). The upshot:

1) A C compiler must accept `static int x[2+1];', because
`2+1' is on the hither side of the line,

2) A C compiler may reject `static int x[strlen("abc")];'
because `strlen("abc")' is on the thither side, but

3) A C compiler may accept `static int x[strlen("abc")];'
under the "other forms" allowance, and need not even
issue a diagnostic.

Cases (2) and (3) illustrate why "It works with my compiler"
does not define the language.

The principle is right, the specific example is wrong.
Expressions that contain a function call and that must
be constant expressions are constraint violations and
must have a diagnostic issued, regardless of whether
an implementation chooses to regard them as constant
expressions.
 
T

Tim Rentsch

Keith Thompson said:
BartC said:
Kaz Kylheku said:
Can I
- use floating point at all ?
- if yes: use sqrt, log, exp ?

No. Constant expressions cannot contain funtion calls.

Yet, an expression such as sqrt(9.0) seems to be reduced to 3.0 on the
first two compilers I tried. [snip]

But where do you draw the line between constant and non-constant
expressions? The authors of the standard already decided exactly
where to draw that line. [snip]

More specifically, they have drawn two lines, one representing
a minimum set of what are constant expressions, and one
representing which constructs are always out of bounds for
constant expressions -- kind of a lower bound and an upper
bound. And function calls are over the "not allowed" line.
 
B

BartC

Tim Rentsch said:
But where do you draw the line between constant and non-constant
expressions? The authors of the standard already decided exactly
where to draw that line. [snip]

More specifically, they have drawn two lines, one representing
a minimum set of what are constant expressions, and one
representing which constructs are always out of bounds for
constant expressions -- kind of a lower bound and an upper
bound. And function calls are over the "not allowed" line.

Perhaps it's about time then that the built-in mathematical functions were
classed as operators, not as arbitrary functions.

Compilers already do that for optimising, but aren't allowed to do that when
the code depends on it (expressions defining array bounds for example,
because it would break on another compiler.)

There are some numeric implications (eg. an array bound could vary slightly
between compilers and machines) but there already are many issues with
floating point anyway. And in examples like the OP's, it wouldn't really
matter.
 
A

army1987

An application would be, given EXPECTED, to define at compile time an
array with EXPECTED + 12*sqrt(EXPECTED) elements, or some even more
complex formula, as required by

I would do this:

#define EXPECTED 15000 /* When changing this, please also change
ARRAY_SIZE accordingly. */
#define ARRAY_SIZE 16470 /* ceil(EXPECTED + 12*sqrt(EXPECTED)) */
 
K

Keith Thompson

Tim Rentsch said:
Expressions that contain a function call and that must
be constant expressions are constraint violations and
must have a diagnostic issued, regardless of whether
an implementation chooses to regard them as constant
expressions.

I'm not convinced that's correct.

N1370 6.6p10 says

An implementation may accept other forms of constant expressions.

To take one example of a context requiring a constant expression,
6.8.4.2p3 says:

The expression of each case label shall be an integer constant
expression and no two of the case constant expressions in the
same switch statement shall have the same value after conversion.

If an implementation accepts `foo()` as a constant expression, then
using `foo()` in a case label doens't violate that constraint, because
it's a constant expression *for that implementation*.

This is different than the general permission to provide extensions
given by 4p6:

A conforming implementation may have extensions (including
additional library functions), provided they do not alter the
behavior of any strictly conforming program.

which does (I think!) require a diagnostic for any code that would
violate a constraint in the absence of the extension.
 
K

Keith Thompson

BartC said:
Tim Rentsch said:
But where do you draw the line between constant and non-constant
expressions? The authors of the standard already decided exactly
where to draw that line. [snip]

More specifically, they have drawn two lines, one representing
a minimum set of what are constant expressions, and one
representing which constructs are always out of bounds for
constant expressions -- kind of a lower bound and an upper
bound. And function calls are over the "not allowed" line.

Perhaps it's about time then that the built-in mathematical functions were
classed as operators, not as arbitrary functions.

Compilers already do that for optimising, but aren't allowed to do that when
the code depends on it (expressions defining array bounds for example,
because it would break on another compiler.)

There are some numeric implications (eg. an array bound could vary slightly
between compilers and machines) but there already are many issues with
floating point anyway. And in examples like the OP's, it wouldn't really
matter.

Sorry, but I don't think that's a good idea. Consider, for example, a
cross compiler where the host and target systems have differing
floating-point precision. (Compilers need to handle this for
floating-point constant, but not for any more complicated expression.)

I think you're understating the problems caused by floating-point
rounding errors. It could be implementation-defined (or unspecified)
a given switch statement is legal, depending on whether the
expressions in two case labels evaluate to the same integer value.

And how often do you really need to use a floating-point function in a
constant expression? I don't think it's worth the additional complexity.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,021
Latest member
AkilahJaim

Latest Threads

Top