10

The classic preprocessor version of the min function looks like

#define min(a, b) ((a) < (b) ? (a) : (b))

This leaves you open to double evaluation—the situation in which you do min(f(), g()), forgetting that f or g has side effects, and you have to spend hours trying to figure out why your function is running twice. To prevent this, you can do

#define min(a, b) ({__typeof__(a) _a = (a); \
    __typeof__(b) _b = (b); \
    _a < _b ? _a : _b;})

This works great under GCC, but if you run it through Clang with -Wgnu set—this set of warnings falls under the umbrella of -pedantic—you get errors like

test.c:5:10: error: use of GNU statement expression extension [-Werror,-Wgnu]
        int a = min(3, 7);
                ^
test.c:1:22: note: expanded from macro 'min'
#  define min(a, b) ({__typeof__(a) _a = (a); __typeof__(b) _b = (b); _a < _b ? _a : _b;})

Is it possible to define these macros in a way that prevents double evaluation and which is acceptable to Clang under -pedantic? (Yes, you can disable the warning with -Wno-gnu, in which case Clang handles the statement expression without a problem. I’m asking because I, like clang -pedantic, am too picky for my own good.)

Edit: I am working in C. I tagged this C++ too because I thought a solution might apply to C++ as well as to C. Oops—forgot about templates! Sorry for the ambiguity.

15
  • 5
    I am curious why both C and C++ tags, which language are you targetting? The answer is different depending on the language. Commented Dec 18, 2013 at 15:19
  • 7
    Simply don't use the preprocessor for it, period. An inline function does the job better and in a more obvious way. And I think in C++ you can even use templates to avoid creating multiple functions for different types. Commented Dec 18, 2013 at 15:19
  • 1
    Why is a C++ answer accepted for this question, where you clearly state that you want C and a C version exists? Commented Dec 18, 2013 at 16:43
  • 1
    @bdesham, I don't understand, the link doesn't add much to what you already had in your question. Can you elaborate, please, why the C answer is less satisfactory? As it appears now, this is a C++ answer to a C question, so it will not serve many others. Commented Dec 18, 2013 at 17:04
  • 1
    @bdesham it is boilerplate that you only have to write once though. Commented Dec 18, 2013 at 18:15

3 Answers 3

13

If you're really writing in C++, simply don't use the preprocessor:

template <typename T>
const T min(const T& a, const T& b)
{
   return (b < a) ? b : a;
}

(Note that I've swapped b and a, so that you get the left-hand operand if the two are equal.)

Otherwise no, not really.

Sign up to request clarification or add additional context in comments.

12 Comments

@Lundin: Because the < operator does not pick one of two operands. It gives you a boolean. Show us your genius C solution.
Here is an article where Andrei Alexandrescu gets very het up about the correct way to write a min/max function template: drdobbs.com/generic-min-and-max-redivivus/184403774.
Even as a C++ answer this isn't a good one, since this doesn't capture the case that a and b may have different types.
@bdesham If you are using C then 1) why tag it as C++ and 2) why accept a C++ answer. You don't make any sense.
It is written in C++ and will not compile in C.
|
8

I think this would work as a C11 solution.

inline
int min(int const x, int const y)
{
    return y < x ? y : x;
}

inline
unsigned minu(unsigned const x, unsigned const y)
{
    return y < x ? y : x;
}

inline
long minl(long const x, long const y)
{
    return y < x ? y : x;
}

inline
unsigned long minul(unsigned long const x, unsigned long const y)
{
    return y < x ? y : x;
}

inline
long long minll(long long const x, long long const y)
{
    return y < x ? y : x;
}

inline
unsigned long long minull(unsigned long long const x, unsigned long long const y)
{
    return y < x ? y : x;
}

inline
float minf(float const x, float const y)
{
    return y < x ? y : x;
}

inline
double mind(double const x, double const y)
{
    return y < x ? y : x;
}

inline
long double minld(long double const x, long double const y)
{
    return y < x ? y : x;
}

#define MIN(X, Y) (_Generic((X) + (Y),   \
    int:                min,             \
    unsigned:           minu,            \
    long:               minl,            \
    unsigned long:      minul,           \
    long long:          minll,           \
    unsigned long long: minull,          \
    float:              minf,            \
    double:             mind,            \
    long double:        minld)((X), (Y)))

4 Comments

+1 but your _Generic expression is much too complicated. _Generic((X)+(Y), int : min, unsigned : minu, ...)((X), (Y)) would suffice. Also you have forgotten the floating point types.
@JensGustedt thanks. I don't think there's a way to get this to work for pointers though without requiring a void* cast.
Pointers could be done through a default case, only that the choice expression as it is now wouldn't work for pointers :(
@JensGustedt I think you'd lose the nicer error message then though if you tried to pass a struct or something.
4

The C11 solution posted by Simple looks ideal, but in case you don't have a C11 compiler, you could still define a macro with intristic type safety (as type safe as C gets anyhow) :

#define MIN(type, X, Y) min_ ## type(X, Y)

This macro will only allow implemented types to be passed, otherwise you will get a compiler error.

Example:

#define MIN(type, X, Y) min_ ## type(X, Y)

long  min_long  (long x, long y);
char  min_char  (char x, char y);
float min_float (float x, float y);

int main()
{
  long  min_l = MIN (long, 5L, 10L);
  char  min_c = MIN (char, 'A', 'B');
  float min_f = MIN (float, 666.66f, 3.14f);

  printf("%ld\n", min_l);
  printf("%c\n", min_c);
  printf("%f\n", min_f);
}

char  min_char  (char x, char y)    { return x < y ? x : y; }
long  min_long  (long x, long y)    { return x < y ? x : y; }
float min_float (float x, float y)  { return x < y ? x : y; }

Now if you would execute the above macro with MIN(int, 1, 2), you would get a compiler error: "min_int, no such function exists".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.