How to make an output function similar to Python's in C?

Viewed 439

I am working on a custom programming language called BPML and I want to try and update the output function called say().

Recent version in C:

void say(char *text) {
    printf("%s", text);
}

Recent version in Python:

def say(text):
    print(text, end = '')

In C, I want it to act like Python's print() function by just typing in the variable to the function, but it's not the case in C, because you still have to use the appropriate kind of variable.

Which is why I got an error in a program I made:

#include <stdio.h>

void say(char *text);

int main() {
    int number = 21;
    say(number);
}

void say(char *text) {
    // Output function
}

Error:

P1.c:7:9: error: incompatible integer to pointer conversion passing 'int' to parameter of type 'char *' [-Werror,-Wint-conversion]
    say(number);
        ^~~~~~

I still have to use the itoa() function in <stdlib.h>, which I didn't have in mind, since it wasn't available in the version of C I use.

So is it possible to make some kind of output function that is similar to the way Python's function acts?

4 Answers

C does not have overloads, nor does it have function templates. But you can a macro and generic selection in C11 and above to choose an appropriate format:

#include <stdio.h>

#define say(X) printf(_Generic((X),    \
                        double: "%f ", \
                        float:  "%f ", \
                        char *: "%s ", \
                        int:    "%d "  \
                ), (X));

int main(void) {
    say(21);
    say(21.5);
    say(21.5f);
    say("Hello world");
}

This can be coupled with X-macros to have really powerful constructs easily:

#include <stdio.h>

// you can easily add new supported types here
#define SAY_FORMATS(X) \
    X(double, "%f")    \
    X(float,  "%f")    \
    X(char *, "%s")    \
    X(int,    "%d")
    
// add space after each item. Leading comma so that we do not
// need to have a dummy entry in the end. Unfortunately C 
// does not like trailing commas in _Generic. Thanks to 
// user694733 for the idea
#define GENERIC_ENTRY(Type, Format) \
    , Type: Format " "

#define say(X)                      \
     printf(_Generic((X)            \
         SAY_FORMATS(GENERIC_ENTRY) \
     ), (X))

int main(void) {
    say(21);
    say(21.5);
    say(21.5f);
    say("Hello world");
}

Whether you're doing this in C, or another language you implement in C, you'll probably need to use something other than C primitives to store your data. In principle, a union has the capability to store multiple data types, but it's probably not flexible enough for what you need.

Compiler implementers typically use structures of this general form to store data of types not known at compile time:

struct GenericData
  {
  int i;
  char *str;
  double d;
  // ... other types
  enum Type t;
  };

Here, the member t indicates which of the various fields contains valid data at any given time -- whether it's representing an integer, or a string, or a floating-point number, or whatever. So you can define your say function like this:

void say (const struct GenericData *data)
  {
  ...
  }

and you'd use it something like this:

GenericData *v = // some function to intialize it
say (v);

Of course, you'd need functions to initialize your values with data of the right type, do math on them, tidy them up after use, etc.

Just as an illustration, the GLib library (used by GTK and Gnome) has built-in support for "variant" data types, using a structure called GVariant:

https://developer.gnome.org/glib/stable/glib-GVariant.html

I imagine that anybody who's ever written a compiler or interpreter will have gone through the process of handling variant types, and will be aware how much work is involved. Don't underestimate the amount of work, or the difficulty.

It's not possible. C does not support type checking in runtime.

Here's the source of Python's print(), although I doubt it will be of much help at all: link

Take a look at the parameters it takes – not a single primitive.

static PyObject *
builtin_print(PyObject *self, PyObject *args, PyObject *kwds)

I decided to expand Antti Haapala's answer to resemble every single format in the printf() function:

#define say(X) printf(_Generic((X),                     \
                      double: "%f", float: "%f",        \
                      char *: "%s", char: "%c",         \
                         int: "%d", unsigned int: "%u", \
                  signed int: "%i", void *: "%p"
               ), (X));

I don't know exactly how to take the formats of %e, %g, %x, %o and %a, as they all take either a floating-point value (float or double) or an unsigned int.

Related