Nested _Generic selections

J

Joel C. Salomon

I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.

[The _Generic keyword is described in <http://bit.ly/c11-n1404+> and
<http://bit.ly/c11-n1441+> by Intel's Clark Nelson, whom I have CC'd on
this message. Clark, can I ask you to respond on <comp.lang.c>?]

Motivation: In the revised IEEE 754 (2008, aka IEC 60559:2011) there is
the function totalOrder(x, y) which is meaningful for each
floating-point type, but meaningless for mixed types. I was wondering
whether this was a candidate for _Generic selection: not simply for
convenience, but for type safety as well.

Consider a simpler case, the functions funcf() & funcd which I want to
wrap in a macro func(x, y) so that
* func(1.0f, 2.0f) calls funcf(),
* func(1.0, 2.0) calls funcd(), and
* func(1.0f, 2.0) & func(1.0, 2.0f) are invalid.

My first effort looked like this:

#include <stdio.h>
void funcf(float x, float y) {
printf("floats %f, %f\n", x, y);
}
void funcd(double x, double y) {
printf("doubles %f, %f\n", x, y);
}
#define func(x, y) _Generic((x), \
float: _Generic((y), float: funcf), \
double: _Generic((y), double: funcd) ) (x, y)
int main(void) {
func(1.0f, 2.0f); // should work, but doesn't
// func(1.0f, 2.0); // shouldn't work
// func(1.0, 2.0f); // shouldn't work
func(1.0, 2.0); // should work, but doesn't
}

I used LLVM Clang version 3.0, which complained about the lines which
I'd intended to have work:

gen.c:12:13: error: controlling expression type 'float' not compatible
with any generic association type
func(1.0f, 2.0f);
~~~~~~~~~~~^~~~~
gen.c:10:20: note: expanded from:
double: _Generic((y), double: funcd) ) (x, y)
^
[And a similar error for the `func(1.0, 2.0)` line.]

First question: It seems that all "legs" of the selection expression
are evaluated; is that correct behavior?

Second question: Assuming that is correct, what might be the best way
to accomplish what I'm trying to? The best method I've come up with is
this:

#define func(x, y) _Generic((x), \
float: _Generic((y), float: funcf, double: 0 ), \
double: _Generic((y), double: funcd, float: 0 ) ) (x, y)
int main(void) {
func(1.0f, 2.0f);
func(1.0f, 2.0);
func(1.0, 2.0f);
func(1.0, 2.0);
}

This "works"; the correct forms compile and the incorrect ones cause
compile errors -- but they obscure the issue:

gen.c:13:2: error: called object type 'int' is not a function or
function pointer
func(1.0f, 2.0);
^~~~~~~~~~~~~~~
gen.c:10:53: note: expanded from:
double: _Generic((y), double: funcd, float: 0 ) ) (x, y)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^

Can this be improved, while working within what the Standard provides?
 
J

Jens Gustedt

Hello Joel,

Am 01/13/2012 03:27 AM, schrieb Joel C. Salomon:
I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.
...

First question: It seems that all "legs" of the selection expression
are evaluated; is that correct behavior?

Yes, I think that it is correct. It is not the only possible behavior,
the standard is unspecific about this. So for your coding you are in
any case better off in assuming the restricted version.
Second question: Assuming that is correct, what might be the best way
to accomplish what I'm trying to?
...
Can this be improved, while working within what the Standard provides?

perhaps something like

#define func(x, y) _Generic((x),
\
float: _Generic((y), float: funcf, double: _Static_assert(0, "float
and double")), \
double: _Generic((y), double: funcd, float: _Static_assert(0, "double
and float"))) (x, y)

would do what you want.

Jens
 
J

Joel C. Salomon

perhaps something like

#define func(x, y) _Generic((x), \
float: _Generic((y), float: funcf, double: _Static_assert(0, "float and double")), \
double: _Generic((y), double: funcd, float: _Static_assert(0, "double and float"))) (x, y)

would do what you want.

Trouble is that _Static_assert is not an expression.

Before you suggest it, `sizeof(int[-1])` won't work either, because of
the "evaluate everything" behavior.

The best I've come up with so far is

#define func(x, y) _Generic((x), \
float: _Generic((y), float: funcf, double: "** float and double **"), \
double: _Generic((y), double: funcd, float: "** double and float **")
) (x, y)

-- which is still buried in the error message.

--Joel
 
J

Jens Gustedt

Am 01/13/2012 07:03 PM, schrieb Jens Gustedt:
perhaps something like

#define func(x, y) _Generic((x),
\
float: _Generic((y), float: funcf, double: _Static_assert(0, "float
and double")), \
double: _Generic((y), double: funcd, float: _Static_assert(0, "double
and float"))) (x, y)

would do what you want.

I notice now, that this doesn't work since _Static_assert is a
statement, which is not allowed in that place.

Something like that might give a bit better diagnostic:

float funcf(float, float);
void funcf_2nd_arg_error(void);
double funcd(double, double);
void funcd_2nd_arg_error(void);

#define check(x, y) \
(*(_Generic((x), float: (float*){ 0 }, double: (double*){ 0 }) \
= &_Generic((y), float: (float){ y }, double: (double){ y })))

#define func(x, y) \
_Generic((x), \
float: _Generic((y), float: funcf, double:
funcf_2nd_arg_error), \
double: _Generic((y), double: funcd, float:
funcd_2nd_arg_error) \
)(x, check(x, y))


Jens
 
I

ImpalerCore

I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.

[The _Generic keyword is described in <http://bit.ly/c11-n1404+> and
<http://bit.ly/c11-n1441+> by Intel's Clark Nelson, whom I have CC'd on
this message.  Clark, can I ask you to respond on <comp.lang.c>?]

Motivation:  In the revised IEEE 754 (2008, aka IEC 60559:2011) there is
the function totalOrder(x, y) which is meaningful for each
floating-point type, but meaningless for mixed types.  I was wondering
whether this was a candidate for _Generic selection: not simply for
convenience, but for type safety as well.

Consider a simpler case, the functions funcf() & funcd which I want to
wrap in a macro func(x, y) so that
* func(1.0f, 2.0f) calls funcf(),
* func(1.0, 2.0) calls funcd(), and
* func(1.0f, 2.0) & func(1.0, 2.0f) are invalid.

My first effort looked like this:

#include <stdio.h>
void funcf(float x, float y) {
        printf("floats %f, %f\n", x, y);}

void funcd(double x, double y) {
        printf("doubles %f, %f\n", x, y);}

#define func(x, y) _Generic((x), \
        float:  _Generic((y), float:  funcf), \
        double: _Generic((y), double: funcd) ) (x, y)
int main(void) {
        func(1.0f, 2.0f);       // should work, but doesn't
//      func(1.0f, 2.0);        // shouldn't work
//      func(1.0,  2.0f);       // shouldn't work
        func(1.0,  2.0);        // should work, but doesn't

}

I used LLVM Clang version 3.0, which complained about the lines which
I'd intended to have work:

gen.c:12:13: error: controlling expression type 'float' not compatible
with any generic association type
        func(1.0f, 2.0f);
        ~~~~~~~~~~~^~~~~
gen.c:10:20: note: expanded from:
        double: _Generic((y), double: funcd) ) (x, y)
                          ^
[And a similar error for the `func(1.0,  2.0)` line.]

First question:  It seems that all "legs" of the selection expression
are evaluated; is that correct behavior?

Second question:  Assuming that is correct, what might be the best way
to accomplish what I'm trying to?  The best method I've come up with is
this:

#define func(x, y) _Generic((x), \
        float:  _Generic((y), float:  funcf, double: 0 ), \
        double: _Generic((y), double: funcd, float:  0 ) ) (x, y)
int main(void) {
        func(1.0f, 2.0f);
        func(1.0f, 2.0);
        func(1.0,  2.0f);
        func(1.0,  2.0);

}

This "works"; the correct forms compile and the incorrect ones cause
compile errors -- but they obscure the issue:

gen.c:13:2: error: called object type 'int' is not a function or
function pointer
        func(1.0f, 2.0);
        ^~~~~~~~~~~~~~~
gen.c:10:53: note: expanded from:
        double: _Generic((y), double: funcd, float:  0 ) ) (x, y)
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^

Can this be improved, while working within what the Standard provides?

Well, if you're willing to party like it's 1999, you can look at this
alternative.

\code
#include <stdio.h>

#define STATIC_ASSERT(name, expr) extern char (name)[(expr) ? 1 : -1]

void func_float( float x, float y )
{
printf( "floats %f, %f\n", x, y );
}

void func_double( double x, double y )
{
printf( "doubles %f, %f\n", x, y );
}

#define func(x, y, type) \
do \
{ \
STATIC_ASSERT( func_type_mismatch, sizeof (x) == sizeof (y) ); \
func_##type( (x), (y) ); \
} while (0)

int main( void )
{
func( 1.0f, 2.0f, float );
//func( 3.0f, 4.0, float );
//func( 5.0, 6.0f, double );
func( 7.0, 8.0, double );

return 0;
}
\endcode

If I uncomment one of those lines out, I get an error message.

test.c: In function 'main':
test.c:25:3: error: size of array 'func_type_mismatch' is negative

Of course, 'sizeof' will not detect differences between types that
happen to have the same size. If there was a 'typeid' operator that
the compiler could use to assign a unique integer identifier for each
unique type, the static assert could wrangle out any type differences
for types that have the same size.

You may disapprove of the extra manual 'type' argument in the macro.
If there was a 'typeof' operator that could determine the type of an
variable, i.e. typeof (2.0f) --> float, you might not need '_Generic'
at all.

\code idea
void func_float( float x, float y );
void func_double( double x, double y );

#define func(x, y) g_func( (x), (y), typeof (x) )
#define g_func(x, y, type) \
do \
{ \
STATIC_ASSERT( func_type_mismatch, typeid (x) == typeid (y) ); \
func_##type( (x), (y) ); \
} while (0)
\endcode

Just some ideas I've been pondering.

Best regards,
John D.
 
J

Jens Gustedt

Am 01/13/2012 09:23 PM, schrieb ImpalerCore:
I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.
Well, if you're willing to party like it's 1999, you can look at this
alternative.

\code
#include <stdio.h>

#define STATIC_ASSERT(name, expr) extern char (name)[(expr) ? 1 : -1]

The problem is as Joel stated in another post that _Static_Assert and
Co are not expressions but statements.

Your Idea of putting things inside a do { ... } while only works for
a functionlike macro that leads to a void expression. If you want the
beast to return a value i.e the end result of the macro to be an
expression and not only a statement, that doesn't work.

Jens
 
S

Shao Miller

Am 01/13/2012 09:23 PM, schrieb ImpalerCore:
I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.
Well, if you're willing to party like it's 1999, you can look at this
alternative.

\code
#include<stdio.h>

#define STATIC_ASSERT(name, expr) extern char (name)[(expr) ? 1 : -1]

The problem is as Joel stated in another post that _Static_Assert and
Co are not expressions but statements.

Party like it's C89:

#define STATIC_ASSERT(name_, expr_) \
(sizeof (struct { char (name_)[(expr_) ? 1 : -1]; }))

int main(void) {
int x;

x = STATIC_ASSERT(is_true, 1);
x = STATIC_ASSERT(is_false, 0);
return 0;
}
 
S

Shao Miller

I'm attempting to use C11's _Generic selection expressions to enforce a
kind of type consistency, and it's failing in an odd way.

[...]

This "works"; the correct forms compile and the incorrect ones cause
compile errors -- but they obscure the issue:

gen.c:13:2: error: called object type 'int' is not a function or
function pointer
func(1.0f, 2.0);
^~~~~~~~~~~~~~~
gen.c:10:53: note: expanded from:
double: _Generic((y), double: funcd, float: 0 ) ) (x, y)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^

Can this be improved, while working within what the Standard provides?

What about:

#define M_STATIC_ASSERT(name_, expr_) \
(sizeof (struct { char (name_)[(expr_) ? 1 : -1]; }))

#define M_IS_FLOAT(x_) \
(_Generic((x_), float: 1, default: 0))

#define M_BOTH_FLOAT(x_, y_) \
(M_IS_FLOAT(x_) && M_IS_FLOAT(y_))

#define M_IS_DOUBLE(x_) \
(_Generic((x_), double: 1, default: 0))

#define M_BOTH_DOUBLE(x_, y_) \
(M_IS_DOUBLE(x_) && M_IS_DOUBLE(y_))

#define M_BOTH_FLOAT_OR_BOTH_DOUBLE(x_, y_) \
(M_BOTH_FLOAT((x_), (y_)) || M_BOTH_DOUBLE((x_), (y_)))

static int funcf(float x, float y) {
(void) x;
(void) y;
return 0;
}

static int funcd(double x, double y) {
(void) x;
(void) y;
return 0;
}

#define func(x_, y_) ( \
(void) M_STATIC_ASSERT( \
func_args_not_both_float_nor_both_double, \
M_BOTH_FLOAT_OR_BOTH_DOUBLE((x_), (y_)) \
), \
_Generic( \
(x_), \
float: funcf, \
double: funcd, \
default: 0 \
)((x_), (y_)) \
)

int main(void) {
int x;

x = func(1.0f, 2.0f);
x = func(1.0f, 2.0);
x = func(1.0, 2.0f);
x = func(1.0, 2.0);
return 0;
}

- Shao Miller
 
J

Joel C. Salomon

What about:

#define M_STATIC_ASSERT(name_, expr_) \
(sizeof (struct { char (name_)[(expr_) ? 1 : -1]; }))

#define M_IS_FLOAT(x_) \
(_Generic((x_), float: 1, default: 0))

#define M_BOTH_FLOAT(x_, y_) \
(M_IS_FLOAT(x_) && M_IS_FLOAT(y_))

#define M_IS_DOUBLE(x_) \
(_Generic((x_), double: 1, default: 0))

#define M_BOTH_DOUBLE(x_, y_) \
(M_IS_DOUBLE(x_) && M_IS_DOUBLE(y_))

#define M_BOTH_FLOAT_OR_BOTH_DOUBLE(x_, y_) \
(M_BOTH_FLOAT((x_), (y_)) || M_BOTH_DOUBLE((x_), (y_)))

#define func(x_, y_) ( \
(void) M_STATIC_ASSERT( \
func_args_not_both_float_nor_both_double, \
M_BOTH_FLOAT_OR_BOTH_DOUBLE((x_), (y_)) \
), \
_Generic( \
(x_), \
float: funcf, \
double: funcd, \
default: 0 \
)((x_), (y_)) \
)

That works; thanks!

I particularly like this technique:

#define M_IS_DOUBLE(x_) \
(_Generic((x_), double: 1, default: 0))

I think I can see a way to make more general use of this.

--Joel
 
S

Shao Miller

What about:

[...code...]
That works; thanks!

I particularly like this technique:

#define M_IS_DOUBLE(x_) \
(_Generic((x_), double: 1, default: 0))

I think I can see a way to make more general use of this.

Are you thinking of something like this?:

#define M_STATIC_ASSERT(name_, expr_) \
(sizeof (struct { char (name_)[(expr_) ? 1 : -1]; }))

#define M_OBJ_IS_TYPE(obj_, type_) \
(_Generic((obj_), type_: 1, default: 0))

#define M_BOTH_ARE_TYPE(x_, y_, type_) \
(M_OBJ_IS_TYPE((x_), type_) && M_OBJ_IS_TYPE((y_), type_))

#define M_REMOVE_PARENS2_OR_MORE(arg_, ...) arg_, __VA_ARGS__

#define M_TYPE_MATCH2_CASE_(type_, m_) \
(M_REMOVE_PARENS2_OR_MORE m_, type_)

#define M_TYPE_MATCH2_CASE(type_, case_, m_) \
(M_BOTH_ARE_TYPE M_TYPE_MATCH2_CASE_(type_, m_)) ||

#define M_TYPE_MATCH2(cases_, x_, y_) ( \
cases_(M_TYPE_MATCH2_CASE, ((x_), (y_))) \
0 \
)

#define M_TYPE_SELECTION_CASE(type_, case_, m_) \
type_: (case_),

#define M_TYPE_SELECTION(subject_, cases_) ( \
_Generic( \
(subject_), \
cases_(M_TYPE_SELECTION_CASE, 0) \
default: 0 \
) \
)

#define M_MAKE_TYPEPAIR_FUNC(func_, x_, y_) ( \
(void) M_STATIC_ASSERT( \
func_args_not_same_type_or_unsupported_types, \
M_TYPE_MATCH2(func_, (x_), (y_)) \
), \
M_TYPE_SELECTION((x_), func_) \
)

static int myfuncf(float x, float y) {
(void) x;
(void) y;
return 0;
}

static int myfuncd(double x, double y) {
(void) x;
(void) y;
return 0;
}

static int myfunci(int x, int y) {
(void) x;
(void) y;
return 0;
}

#define myfunc_(unit_, m_) \
unit_(float, myfuncf, m_) \
unit_(double, myfuncd, m_) \
unit_(int, myfunci, m_)

#define myfunc(x_, y_) \
(M_MAKE_TYPEPAIR_FUNC(myfunc_, (x_), (y_))((x_), (y_)))

int main(void) {
int x;

x = myfunc(1.0f, 2.0f);
x = myfunc(1.0f, 2.0);
x = myfunc(1.0, 2.0f);
x = myfunc(1.0, 2.0);
x = myfunc(13, 42);
x = myfunc(13.3, 42);
return 0;
}

- Shao Miller
 

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,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top