Skip to content

Instantly share code, notes, and snippets.

@easyaspi314
Last active May 19, 2021 15:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save easyaspi314/1b7a60378cef070abd568b4dbdede573 to your computer and use it in GitHub Desktop.
Save easyaspi314/1b7a60378cef070abd568b4dbdede573 to your computer and use it in GitHub Desktop.
iostream.h: cin and cout for C using GCC and C11 extensions

iostream.h

iostream.h (ab?)uses _Generic, __builtin_choose_expr, __typeof__, statement expressions, and recursive variadic macro expansion to create a cin and cout for C11+GCC extension compilers.

Should you use this? Of course not. But it's pretty f***ing cool.

Usage

#include <stdbool.h>
#include <stdio.h>
#include "iostream.h"

int main(void) {
    int x;
    cout("Give me an int!", '\n');
    cin(x);
    cout(x, endl);
    
    double d;
    cout("Give me a double!", "\n");
    cin(d);
    cout(x, endl);
    
    char buf[21];
    cout("Give me a string (20 chars. max, but I won't overflow, I promise)!", endl);
    cin(buf, sizeof(buf));
    cout(buf, '\n');
    
    char *buf = NULL;
    char **buf_p = &buf;
    cout("Give me a string and go nuts!", endl, "Any length, I don't mind!\n");
    cin(buf_p);
    cout(buf, "\nDid you doubt me?\n");
    free(buf);
    
    bool b;
    cout("Give me a number that is either zero or non-zero", endl);
    cin(b);
    cout("is your number zero? The answer is ", b, endl);
    cout("true or false?", endl);
    cin(b, boolalpha);
    cout("You picked ", b, endl);
}
/* Copyright (C) 2019 easyaspi314 (Devin).
* Released under the MIT license.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom
* the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall
* be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE. */
/* iostream.h: cin and cout, for C.
* Requires a recent GCC or Clang version with support for C11,
* __builtin_choose_expr, and _Generic, and preferrably it should
* have a getline implementation, although it isn't required.
*
* Usage: See below. */
#define __STDC_WANT_LIB_EXT2__ 1
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
// https://github.com/swansontec/map-macro
#include "map.h"
// Clears the data left over by scanf.
static inline void _clear_stdin(void)
{
int _c;
while ((_c = getchar()) != '\n' && _c != EOF)
;
}
/// A wrapper for fgets which also null terminates the string and doesn't
/// leave anything in stdin.
__attribute__((__unused__))
static int _length_getline(char *_x, size_t _length)
{
if (_x == NULL || _length < 1) {
return EOF;
}
memset(_x, 0, _length);
// Skip whitespace and read the first character.
if (scanf(" %c", _x) == EOF) {
return EOF;
}
if (_length == 2) {
return 1;
}
// Read starting at the first character because we just got it from
// scanf.
if (fgets(_x + 1, (int)_length - 1, stdin) == NULL) {
return EOF;
}
// Find the newline in the string. Replace it with '\0' if it exists,
// or clear out the line because there is data left over.
char *_newline = strchr(_x, '\n');
if (_newline != NULL) {
*_newline = '\0';
} else {
// There is extra data, ignore it.
_clear_stdin();
}
return 1;
}
/// Dynamically allocates a buffer large enough to store the next line from stdin, null terminates it (removing '\n'),
/// and stores it in _x.
__attribute__((__unused__))
static int _malloc_getline(char **_x)
{
#if !defined(IOSTREAM_DISABLE_GETLINE) \
&& (defined(__STDC_ALLOC_LIB__) /* C2x hyyype! */ \
|| (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200803L) || defined(_GNU_SOURCE) /* glibc */ \
|| defined(_WITH_GETLINE) /* BSD */ \
)
if (_x == NULL) {
return EOF;
}
char _tmp;
// Skip whitespace until the first character.
if (scanf(" %c", &_tmp) == EOF) {
return EOF;
}
// We have to put that first one back and read again.
// getline will (re)allocate memory and you can't just give an
// offset pointer to realloc.
ungetc(_tmp, stdin);
// Set up *_x for getline
*_x = NULL;
size_t _size = 0;
// Read the line
if (getline(_x, &_size, stdin) == -1) {
free(*_x);
*_x = NULL;
return EOF;
}
// Replace '\n' with '\0'.
char *_newline = strchr(*_x, '\n');
if (_newline != NULL) {
*_newline = '\0';
}
return 1;
#else // No getline. :(
if (_x == NULL) {
return EOF;
}
// 64 should be a decent size.
int _size = 64;
int _offset = 1;
*_x = malloc((size_t)_size + 2);
if (*_x == NULL) {
return EOF;
}
// Skip whitespace and read the first character
if (scanf(" %c", *_x) == EOF) {
free(*_x);
*_x = NULL;
return EOF;
}
// Read in chunks starting at the first character.
while (fgets((*_x) + _offset, _size + 1, stdin) != NULL) {
char *_newline;
// Find the newline and replace it if we can
if ((_newline = strchr((*_x) + _offset, '\n')) != NULL) {
*_newline = '\0';
return 1;
} else if (feof(stdin)) {
// We might be at the end.
return 1;
} else {
// Grow the buffer by _size
_offset += _size;
char *_tmp = realloc(*_x, (size_t)(_size + _offset + 1));
if (_tmp == NULL) {
free(*_x);
*_x = NULL;
return EOF;
}
*_x = _tmp;
}
}
// shouldn't be able to get here but ok whatever
return EOF;
#endif // No getline
}
// Reads either "true" or "false" from stdin like std::boolalpha if _alpha
// is true, or reads an integer and converts to bool.
static int _read_bool(_Bool *_b, _Bool _alpha)
{
if (_b == NULL) {
return EOF;
}
if (_alpha) {
char _buf[7];
// We read just enough to get a full word plus a space.
if (scanf("%6s", _buf) == EOF) {
return EOF;
}
if (strlen(_buf) == 5 && !strcmp(_buf, "false")) {
*_b = 0;
} else if (strlen(_buf) == 4 && !strcmp(_buf, "true")) {
*_b = 1;
} else {
return EOF;
}
return 1;
} else {
long long val = 0;
// Read into long long because it is the largest type we can have.
// Otherwise, truncation might cause issues.
if (scanf("%lli", &val) < 1) {
return EOF;
}
// "cast to bool"
*_b = !!val;
return 1;
}
}
// clang-format doesn't handle _Generic properly.
// clang-format off
// To make cin work with strict aliasing.
union _iostream_pun {
_Bool *_b;
char *_c;
char **_char_p;
unsigned char *_hhu;
signed char *_hhi;
unsigned short *_hu;
signed short *_hi;
unsigned int *_u;
signed int *_i;
unsigned long *_lu;
signed long *_li;
unsigned long long *_llu;
signed long long *_lli;
float *_f;
double *_lf;
long double *_Lf;
void *_p;
};
/// See below.
#define _cin_impl(x, len) __extension__ ({ \
union _iostream_pun _x = { ._p = &(x) }; \
int _tmp = _Generic((x), \
_Bool: _read_bool(_x._b, len), \
char: scanf(" %c", _x._c), \
signed char: scanf("%hhi", _x._hhi), \
unsigned char: scanf("%hhu", _x._hhu), \
signed short: scanf("%hi", _x._hi), \
unsigned short: scanf("%hu", _x._hu), \
signed int: scanf("%i", _x._i), \
unsigned int: scanf("%u", _x._u), \
signed long: scanf("%li", _x._li), \
unsigned long: scanf("%lu", _x._lu), \
signed long long: scanf("%lli", _x._lli), \
unsigned long long: scanf("%llu", _x._llu), \
float: scanf("%f", _x._f), \
double: scanf("%lf", _x._lf), \
long double: scanf("%Lf", _x._Lf), \
/* uintptr_t hides the "cast to pointer from smaller size" warning */ \
char **: _malloc_getline((char **)((uintptr_t)(x))), \
char *: _length_getline((char *)((uintptr_t)(x)), len /* bufsize */) \
); \
/* set the value to zero on errors like std::cin. */ \
if (_tmp < 1) { \
_Generic((x), \
char *: (void)0, char **: (void)0, default: (void)memset(_x._p, 0, sizeof(x)) \
); \
} \
/* Don't clear when we have char * or char ** as those do that automatically. */ \
_Generic((x), char *: (void)0, char **: (void)0, default: _clear_stdin()); \
_tmp; \
})
/// int cin(T x, [size_t len|bool boolalpha]):
/// Reads a line from stdin into x using _Generic to determine the type.
/// For char *, the len argument is required and it sets the limit to how long the input
/// will be including a null terminator.
/// For both char * and char **, the string will be null terminated, with any newlines
/// removed.
///
/// Usage:
/// // Basic arithmetic types can be read like so:
/// {int, char, signed char, unsigned long long, float, etc} x;
/// cin(x);
///
/// // Truncated strings pass the length (including the null terminator) as a second argument.
/// char str[21];
/// cin(str, sizeof(str));
///
/// // A dynamically allocated line will be recieved like so
/// char *buf = NULL;
/// char **tmp = &buf;
/// cin(tmp);
/// // don't forget to free!
///
/// Caveats:
/// - To read a character, use 'char'. To read an 8-bit signed integer, use 'signed char',
/// and for an 8-bit unsigned integer, use 'unsigned char'.
/// - Hex and octal are only detected with signed variables, due to the limitations of
/// scanf.
/// - As shown above, you need a temporary variable set to the address of a null char
/// pointer to get the automatic memory allocation to work. The buffer must be freed.
///
/// Returns EOF on an error, or anything else otherwise.
#define cin(...) _cin_impl_wrap(__VA_ARGS__, 0, 0)
/// Wrapper for cin that prevents variadic macro warnings.
#define _cin_impl_wrap(x, len, ...) _cin_impl(x, len + 0)
#define boolalpha 1
/// For those who prefer it.
#define endl ((char)'\n')
/// Attempts to use lexical parsing to tell whether an int is a char literal or not.
/// See below for how it works.
#define _is_char_literal(x) (sizeof(#x) > 3 && ((#x)[0] == '\'' || (#x)[sizeof(#x) - 2] == '\''))
/// Prints x to the console. See cout below.
#define _cout_impl(x) __extension__({ \
/* typeof doesn't decay array to pointer. We can however use __builtin_choose_expr \
* to either select the type of the argument or const char *. */ \
__typeof__( \
__builtin_choose_expr(_Generic((x), char *: 0, const char *: 0, default: 1), \
(x), \
(const char *)NULL) \
) _x = (x); \
/* XXX: Does this violate strict aliasing? GCC doesn't warn about it. */ \
const void *_val = &_x; \
_Generic((x), \
_Bool: fputs((*(const _Bool *)(_val)) ? "true" : "false", stdout), \
char: putchar(*(const char *)(_val)), \
signed char: printf("%hhi", *(const signed char *)(_val)), \
unsigned char: printf("%hhu", *(const unsigned char *)(_val)), \
signed short: printf("%hi", *(const signed short *)(_val)), \
unsigned short: printf("%hu", *(const unsigned short *)(_val)), \
signed int: (_is_char_literal(x) ? putchar(*(const signed int *)(_val)) \
: printf("%i", *(const signed int *)(_val))), \
unsigned int: (_is_char_literal(x) ? putchar(*(const signed int *)(_val)) \
: printf("%u", *(const unsigned int *)(_val))), \
signed long: printf("%li", *(const signed long *)(_val)), \
unsigned long: printf("%lu", *(const unsigned long *)(_val)), \
signed long long: printf("%lli", *(const signed long long *)(_val)), \
unsigned long long: printf("%llu", *(const unsigned long long *)(_val)), \
float: printf("%f", (const double)*(const float *)(_val)), \
double: printf("%lf", *(const double *)(_val)), \
long double: printf("%Lf", *(const long double *)(_val)), \
char *: fputs((const char *)((const uintptr_t)(_x)), stdout), \
const char *: fputs((const char *)((const uintptr_t)(_x)), stdout), \
void *: printf("%p", (const void *)(const uintptr_t)(x)) \
); \
})
/// Expand the cout statement into a ternary that will avoid calling further _cout_impls in
/// the case of an error.
#define _cout_expand(x) (_cout_impl(x) == EOF) ? EOF :
/// int cout(...):
/// Prints one or more items to stdout, using _Generic to deduce the proper types
/// Usage:
/// {int, char, unsigned int, const char *} x = ...;
/// cout(x, cin_endl);
///
/// cout("Hello world");
///
/// Note: Because character literals are int by default, detection of these is a little
/// hacky.
/// If the literal LEXICALLY begins or ends in a single quote (') or is manually cast
/// to char, it will use putchar. As in, the actual macro parameters are stringified and
/// checked.
///
/// cout('a'); // a
/// cout(('a')); // 97
/// cout((char)('a')); // a
/// cout('a' + 1); // b
/// cout(1 + 'a'); // b
/// cout(1 + 'a' + 2); // 100
/// int c = 'a';
/// cout(c); // 97
/// cout((char)c); // a
///
/// Returns:
#define cout(...) __extension__ ({ MAP(_cout_expand, __VA_ARGS__) EOF; })
// clang-format on
#ifdef IOSTREAM_DEMO
#include <stdbool.h>
// Just a little demo/test.
int main(void)
{
const char *space1 = " ";
char space2[] = " ";
// should print Hello 1 2 3
cout((char)72, 'd' + 1, 3 + 'i', "l", 'o', ' ', 0x1, space1, 2ll, space2, (unsigned short)3u, endl);
int x;
cout("int: ");
cin(x);
cout(x, endl);
bool b;
cout("bool (nonzero): ");
cin(b);
cout(b, endl);
cout("bool (\"true\" or \"false\"): ");
cin(b, boolalpha);
cout(b, endl);
char y;
cout("char: ");
cin(y);
cout(y, endl);
char buf[21];
cout("string (20 max): ");
cin(buf, sizeof(buf));
cout(buf, endl);
char* buf_alloc = NULL;
char** buf_alloc_p = &buf_alloc;
cout("string: (any length): ");
cin(buf_alloc_p);
cout(buf_alloc, endl);
free(buf_alloc);
float f;
cout("float: ");
cin(f);
cout(f, endl);
}
#endif
/*
* Copyright (C) 2012 William Swanson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the names of the authors or
* their institutions shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without
* prior written authorization from the authors.
*/
/* https://github.com/swansontec/map-macro */
#ifndef MAP_H_INCLUDED
#define MAP_H_INCLUDED
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,
#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next)
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next)
#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)
/**
* Applies the function macro `f` to each of the remaining parameters.
*/
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
/**
* Applies the function macro `f` to each of the remaining parameters and
* inserts commas between the results.
*/
#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment