Problem: using an unsigned type as input into a CPP-psueudotemplatecauses 'illegal expression' error

A

Andrey Vul

The "template":

#include <limits.h>

#define issignedtype(t) ((t)-1 < (t)0 ? 1 : 0)
#define _T_MAX(t) \
inline t t##_max() { \
t x = 0; \
if (issignedtype(t)) *((unsigned t*)&x) = \
(1ULL << (sizeof(t)*CHAR_BIT - 1)) - 1; \
else x = (t)-1; \
return x; \
}


When expanded with t=size_t,
inline size_t size_t_max() {
size_t x = 0;
if (((size_t)-1 < (size_t)0 ? 1 : 0))
*((unsigned size_t*)&x) = /* ERROR: missing ')' before size_t
* 'size_t': illegal use of this
type as an expression
* syntax error: ')' */
(1ULL << (sizeof(size_t)*8 - 1)) - 1;
else x = (size_t)-1;
return x; }

The expansion with t=int generates no errors.
Any solutions?
 
A

Andrey Vul

     Why not just `( (t)-1 < 0 )'?
Midterm brain drain.
     Others have explained why this doesn't work.  As an alternative,
you might try

        #define _T_MAX(t) \
            ((t)-1 < 0 \
                ? (t)1 << (CHAR_BIT * sizeof(t) - 2) \
                : (t)-1 )
Wouldn't _T_MAX(int) evaluate to 1<<30 = 0x40000000 instead of
0x7ffffff, which is what I'm trying to do?
For unsigned, it works fine, but for signed (2-complement), yeah...
That's why I'm punning the pointer to unsigned, so that (t=int) x =
(int)(0x80000000ULL - 1) = 0x7ffffff
Advantages: Works for signed and unsigned integers, works even when
`t' is a multi-word type name like `long long', is an expression
rather than a function definition and hence is not restricted to
file scope, elicits a diagnostic if `t' is a floating-point type.

Disadvantages (shared with the original): Uses the reserved name
_T_MAX, assumes absence of padding bits.

_T_MAX is reserved? In which standard?
 
A

Andrey Vul

Wouldn't _T_MAX(int) evaluate to 1<<30 = 0x40000000 instead of
0x7ffffff, which is what I'm trying to do?
For unsigned, it works fine, but for signed (2-complement), yeah...
That's why I'm punning the pointer to unsigned, so that (t=int) x =
(int)(0x80000000ULL - 1) = 0x7ffffff
That is, to prevent 2-complement truncation from occuring in an
intermediate expression.
 
A

Andrey Vul

     Ooops.  It would, indeed.  I got only the highest-order value
bit; need to work a little harder to get the others:

        ( (t)1 << (CHAR_BIT * sizeof(t) - 2)
          + ((t)1 << (CHAR_BIT * sizeof(t) - 2) - 1 )

The convoluted arrangement is to avoid attempting to calculate
a `t' value greater than t_MAX.
Convoluted? It's still more elegant than my type-punning uncompilable
monster.
     ISO/IEC 9899:1999, "Programming languages — C," of course.
Specifically, this snippet from 7.1.3p1:

        All identifiers that begin with an underscore and either
        an uppercase letter or another underscore are always
        reserved for any use.

_ meant internal reserved kludge.
Completely inappropriate in a header file.
 
B

Ben Bacarisse

Andrey Vul said:
Midterm brain drain.
Wouldn't _T_MAX(int) evaluate to 1<<30 = 0x40000000 instead of
0x7ffffff, which is what I'm trying to do?
For unsigned, it works fine, but for signed (2-complement), yeah...
That's why I'm punning the pointer to unsigned, so that (t=int) x =
(int)(0x80000000ULL - 1) = 0x7ffffff

Yes, but you can't "generate" the unsigned type from the signed one
(that was your original question) so the punning is possible. Anyway,
I don't see the need for type punning myself. If you are prepared to
put up with the assumptions that are implied by it, I think

x = (1ULL << sizeof(t) * CHAR_BIT - 1) - 1

is just as good. It assumes C99 and it won't work if there are
padding bits, but then that applies to your code too.

I image Eric was keen to avoid assuming there is an unsigned long long
type big enough for the job. One way to do that would be

((((t)1 << CHAR_BIT * sizeof(t) - 2) - 1) << 1) + 1

This has the advantage of working even when t is an extended integer
type that is wider than unsigned long long. I can't shake the feeling
there is a simpler way, but I also suspect there is no way that does
not make some assumptions about the implementation.

_T_MAX is reserved? In which standard?

The C standard!
 
A

Andrey Vul

        ( (t)1 << (CHAR_BIT * sizeof(t) - 2)
          + ((t)1 << (CHAR_BIT * sizeof(t) - 2) - 1 )
The compiler correctly warns about missing brackets around the '<<':
Corrected:
((t)1 << (CHAR_BIT * sizeof(t) - 2))
+ (((t)1 << (CHAR_BIT * sizeof(t) - 2)) - 1)
 
A

Andrey Vul

is just as good.  It assumes C99 and it won't work if there are
padding bits, but then that applies to your code too.
It also won't work on structs or floating-point values.
But it's commented right above the (revised) macro definition:
/* Find the maximum value that an integer type (without padding bits)
can hold. */
#define T_MAX(t) (issignedtype(t) ? ((t)1 << (CHAR_BIT * sizeof(t) -
2)) \
+ (((t)1 << (CHAR_BIT * sizeof(t) - 2)) - 1) \
: (t)-1)
The C standard!
89 or 99?
 
H

Harald van Dijk

You realize that passing a signed integer type to the macro above
produces undefined behavior? Merely evaluating the "(t)-1" causes
arithmetic underflow on the signed type, generating UB. One possible
consequence of undefined behavior is that the expression could yield 0
with signed types.

I don't see it. For signed types, -1 is within the type's range, right?
Could you explain further?
 
V

vippstar

It also won't work on structs or floating-point values.
But it's commented right above the (revised) macro definition:
/* Find the maximum value that an integer type (without padding bits)
can hold. */
#define T_MAX(t) (issignedtype(t) ? ((t)1 << (CHAR_BIT * sizeof(t) -
2)) \
                                                                        + (((t)1 << (CHAR_BIT * sizeof(t) - 2)) - 1) \
                                                                : (t)-1)

((t)1 << (CHAR_BIT * sizeof (t) - 2))) is not necessarily an
expression with the highest bit set. The code breaks when the signed
integer type does not have CHAR_BIT * sizeof (T) - 1 value bits, and
undefined behavior is invoked. If the max/min values of the signed
integers could be found at compile-time, the macro constants in
89 or 99?

In both, the identifier interferes with the implementations namespace.
_[A-Z] is reserved for the implementation.
 
R

Richard Tobin

#define issignedtype(t) ((t)-1 < (t)0 ? 1 : 0)
[/QUOTE]
You realize that passing a signed integer type to the macro above
produces undefined behavior? Merely evaluating the "(t)-1" causes
arithmetic underflow on the signed type, generating UB.

That's a cast, not a subtraction. t is a type, not an integer.

-- Richard
 
B

Ben Bacarisse

((t)1 << (CHAR_BIT * sizeof (t) - 2))) is not necessarily an
expression with the highest bit set. The code breaks when the signed
integer type does not have CHAR_BIT * sizeof (T) - 1 value bits, and
undefined behavior is invoked.

I think Andrey Vul is aware of that. In fact he was complaining about
my saying it since it is commented above the code!

<snip>
 
E

Eric Sosman

((t)1 << (CHAR_BIT * sizeof (t) - 2))) is not necessarily an
expression with the highest bit set. The code breaks when the signed
integer type does not have CHAR_BIT * sizeof (T) - 1 value bits, and
undefined behavior is invoked. [...]

Yes, that fault was acknowledged in the first posting
of the expression, two days ago.
 
V

vippstar

(e-mail address removed) writes:

I think Andrey Vul is aware of that.  In fact he was complaining about
my saying it since it is commented above the code!

<snip>

I'm sorry, I hadn't noticed this, now that I read the discussion again
I see it.
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top