/* String descriptors.
   Copyright (C) 2023-2025 Free Software Foundation, Inc.

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   This file is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

/* Written by Bruno Haible <bruno@clisp.org>, 2023.  */

#ifndef _STRING_DESC_H
#define _STRING_DESC_H 1

/* This file uses _GL_INLINE_HEADER_BEGIN, _GL_INLINE,
   _GL_ATTRIBUTE_NODISCARD, _GL_ATTRIBUTE_NONNULL,
   _GL_ATTRIBUTE_NONNULL_IF_NONZERO.  */
#if !_GL_CONFIG_H_INCLUDED
 #error "Please include config.h first."
#endif

/* Get ptrdiff_t.  */
#include <stddef.h>

/* Get FILE.  */
#include <stdio.h>

/* Get abort(), free().  */
#include <stdlib.h>

/* Get idx_t.  */
#include "idx.h"

/* Whether the compiler supports statement-expressions.
   Test program:
     int f (int x) { return ({ x; x; }); }
 */
#if defined __GNUC__ || defined __clang__ \
    || (defined __SUNPRO_C && __SUNPRO_C >= 0x5150)
# define HAVE_STATEMENT_EXPRESSIONS 1
#endif

/* Whether the compiler supports _Generic.
   Test program:
     int f (int x) { return _Generic (x, char *: 2, int: 3); }
 */
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 9) > 4) \
    || (defined __clang__ && __clang_major__ >= 3) \
    || (defined __SUNPRO_C && __SUNPRO_C >= 0x5150)
# define HAVE__GENERIC 1
#endif

/* Whether the compiler supports __builtin_choose_expr.
   _Generic and __builtin_choose_expr are like conditional expressions,
   except that the return types of the branches need not match: They avoid an
   "error: type mismatch in conditional expression".
   Test program:
     int f (int x) { return __builtin_choose_expr (sizeof (x) == 4, 2, 3); }
 */
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 1) > 3) \
    || (defined __clang__ && __clang_major__ >= 3)
# define HAVE_BUILTIN_CHOOSE_EXPR 1
#endif

/* Whether the compiler supports __builtin_constant_p and whether that built-in
   returns true for string literals.
   _Generic has an antiquated treatment of string literals: It treats string
   literals like 'char *', not 'const char *'.  To compensate for this, we need
   __builtin_constant_p.
   Test program:
     int i, v[__builtin_constant_p ("x") > __builtin_constant_p (i) ? 1 : -1];
 */
#if (defined __GNUC__ && __GNUC__ + (__GNUC_MINOR__ >= 1) > 3) \
    || (defined __clang__ && __clang_major__ >= 4)
# define HAVE_BUILTIN_CONSTANT_P 1
#endif

/* Whether we support rw_string_desc_t as distinct from string_desc_t.  */
#if HAVE_STATEMENT_EXPRESSIONS && HAVE__GENERIC && HAVE_BUILTIN_CHOOSE_EXPR \
    && HAVE_BUILTIN_CONSTANT_P
# define HAVE_RW_STRING_DESC 1
#endif

_GL_INLINE_HEADER_BEGIN
#ifndef GL_STRING_DESC_INLINE
# define GL_STRING_DESC_INLINE _GL_INLINE
#endif

#ifdef __cplusplus
extern "C" {
#endif


/* Type describing a string that may contain NUL bytes.
   It's merely a descriptor of an array of bytes.
   It comes in two flavours:
     - string_desc_t is read-only,
     - rw_string_desc_t is read-write.
   When we write
     [rw_]string_desc_t
   it means either string_desc_t or rw_string_desc_t.  */
typedef struct rw_string_desc_t rw_string_desc_t;
struct rw_string_desc_t
{
  /* The fields of this struct should be considered private.  */
  idx_t _nbytes;
  char *_data;
};
#if HAVE_RW_STRING_DESC
typedef struct string_desc_t string_desc_t;
struct string_desc_t
{
  /* The fields of this struct should be considered private.  */
  idx_t _nbytes;
  const char *_data;
};
#else
typedef rw_string_desc_t string_desc_t;
#endif

/* String descriptors can be passed and returned by value.

   String descriptors and NUL-terminated 'const char *'/'char *' C strings
   cannot be used interchangeably.  You will get compilation errors if you
   attempt to assign a string descriptor to a C string or vice versa.  */


/* ==== Conversions between string descriptors ==== */

/* Return a read-only view of S.  */
#if 0 /* Defined inline below.  */
extern string_desc_t sd_readonly (rw_string_desc_t s);
#endif

/* Return a read-only view of S.
   Attention: This function is as dangerous as a cast from 'const char *'
   to 'char *'!  */
#if 0 /* Defined inline below.  */
extern rw_string_desc_t sd_readwrite (string_desc_t s);
#endif


/* ==== Side-effect-free operations on string descriptors ==== */

/* Return the length of the string S.  */
#if 0 /* Defined inline below.  */
extern idx_t sd_length ([rw_]string_desc_t s);
#endif

/* Return the byte at index I of string S.
   I must be < length(S).  */
#if 0 /* Defined inline below.  */
extern char sd_char_at ([rw_]string_desc_t s, idx_t i);
#endif

/* Return a read-only view of the bytes of S.  */
#if 0 /* Defined inline below.  */
extern const char * sd_data (string_desc_t s);
extern       char * sd_data (rw_string_desc_t s);
#endif

/* Return true if S is the empty string.  */
#if 0 /* Defined inline below.  */
extern bool sd_is_empty ([rw_]string_desc_t s);
#endif

/* Return true if A and B are equal.  */
#if 0 /* Defined inline below.  */
extern bool sd_equals ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif

/* Return true if S starts with PREFIX.  */
#if 0 /* Defined inline below.  */
extern bool sd_startswith ([rw_]string_desc_t s, [rw_]string_desc_t prefix);
#endif

/* Return true if S ends with SUFFIX.  */
#if 0 /* Defined inline below.  */
extern bool sd_endswith ([rw_]string_desc_t s, [rw_]string_desc_t suffix);
#endif

/* Return > 0, == 0, or < 0 if A > B, A == B, A < B.
   This uses a lexicographic ordering, where the bytes are compared as
   'unsigned char'.  */
#if 0 /* Defined inline below.  */
extern int sd_cmp ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif

/* Return > 0, == 0, or < 0 if A > B, A == B, A < B.
   Either A or B must be entirely ASCII.
   This uses a lexicographic ordering, where the bytes are compared as
   'unsigned char', ignoring case, in the "C" locale.  */
#if 0 /* Defined inline below.  */
extern int sd_c_casecmp ([rw_]string_desc_t a, [rw_]string_desc_t b);
#endif

/* Return the index of the first occurrence of C in S,
   or -1 if there is none.  */
#if 0 /* Defined inline below.  */
extern ptrdiff_t sd_index ([rw_]string_desc_t s, char c);
#endif

/* Return the index of the last occurrence of C in S,
   or -1 if there is none.  */
#if 0 /* Defined inline below.  */
extern ptrdiff_t sd_last_index ([rw_]string_desc_t s, char c);
#endif

/* Return the index of the first occurrence of NEEDLE in HAYSTACK,
   or -1 if there is none.  */
#if 0 /* Defined inline below.  */
extern ptrdiff_t sd_contains ([rw_]string_desc_t haystack, [rw_]string_desc_t needle);
#endif

/* Return an empty string.  */
extern string_desc_t sd_new_empty (void);

/* Construct and return a string of length N, at the given memory address.  */
#if 0 /* Defined inline below.  */
extern string_desc_t sd_new_addr (idx_t n, const char *addr)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
extern rw_string_desc_t sd_new_addr (idx_t n, char *addr)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#endif

/* Return a string that represents the C string S, of length strlen (S).  */
extern string_desc_t sd_from_c (const char *s);

/* Return the substring of S, starting at offset START and ending at offset END.
   START must be <= END.
   The result is of length END - START.
   The result must not be freed (since its storage is part of the storage
   of S).  */
#if 0 /* Defined inline below.  */
extern string_desc_t sd_substring (string_desc_t s, idx_t start, idx_t end);
extern rw_string_desc_t sd_substring (rw_string_desc_t s, idx_t start, idx_t end);
#endif

/* Output S to the file descriptor FD.
   Return 0 if successful.
   Upon error, return -1 with errno set.  */
#if 0 /* Defined inline below.  */
extern int sd_write (int fd, [rw_]string_desc_t s);
#endif

/* Output S to the FILE stream FP.
   Return 0 if successful.
   Upon error, return -1.  */
#if 0 /* Defined inline below.  */
extern int sd_fwrite (FILE *fp, [rw_]string_desc_t s);
#endif


/* ==== Memory-allocating operations on string descriptors ==== */

/* Construct a string of length N, with uninitialized contents.
   Return 0 if successful.
   Upon error, return -1 with errno set.  */
_GL_ATTRIBUTE_NODISCARD
extern int sd_new (rw_string_desc_t *resultp, idx_t n);

/* Construct a string of length N, filled with C.
   Return 0 if successful.
   Upon error, return -1 with errno set.  */
_GL_ATTRIBUTE_NODISCARD
extern int sd_new_filled (rw_string_desc_t *resultp, idx_t n, char c);

/* Construct a copy of string S.
   Return 0 if successful.
   Upon error, return -1 with errno set.  */
#if 0 /* Defined inline below.  */
_GL_ATTRIBUTE_NODISCARD
extern int sd_copy (rw_string_desc_t *resultp, [rw_]string_desc_t s);
#endif

/* Construct the concatenation of N strings.  N must be > 0.
   Return 0 if successful.
   Upon error, return -1 with errno set.  */
_GL_ATTRIBUTE_NODISCARD
extern int sd_concat (rw_string_desc_t *resultp, idx_t n, /* [rw_]string_desc_t string1, */ ...);

/* Construct a copy of string S, as a NUL-terminated C string.
   Return it is successful.
   Upon error, return NULL with errno set.  */
#if 0 /* Defined inline below.  */
extern char * sd_c ([rw_]string_desc_t s) _GL_ATTRIBUTE_DEALLOC_FREE;
#endif


/* ==== Operations with side effects on string descriptors ==== */

/* Overwrite the byte at index I of string S with C.
   I must be < length(S).  */
extern void sd_set_char_at (rw_string_desc_t s, idx_t i, char c);

/* Fill part of S, starting at offset START and ending at offset END,
   with copies of C.
   START must be <= END.  */
extern void sd_fill (rw_string_desc_t s, idx_t start, idx_t end, char c);

/* Overwrite part of S with T, starting at offset START.
   START + length(T) must be <= length (S).  */
#if 0 /* Defined inline below.  */
extern void sd_overwrite (rw_string_desc_t s, idx_t start, [rw_]string_desc_t t);
#endif

/* Free S.  */
extern void sd_free (rw_string_desc_t s);


/* ==== Inline function definitions ==== */

#if HAVE_RW_STRING_DESC
GL_STRING_DESC_INLINE string_desc_t
sd_readonly (rw_string_desc_t s)
{
  string_desc_t result;
  result._nbytes = s._nbytes;
  result._data = s._data;
  return result;
}
#else
# define sd_readonly(s) (s)
#endif

#if HAVE_RW_STRING_DESC
GL_STRING_DESC_INLINE rw_string_desc_t
sd_readwrite (string_desc_t s)
{
  rw_string_desc_t result;
  result._nbytes = s._nbytes;
  result._data = (char *) s._data;
  return result;
}
#else
# define sd_readwrite(s) (s)
#endif

#if HAVE_RW_STRING_DESC
# define sd_length(s) \
   _Generic (s, \
             string_desc_t    : (s)._nbytes, \
             rw_string_desc_t : (s)._nbytes)
#else
GL_STRING_DESC_INLINE idx_t
sd_length (string_desc_t s)
{
  return s._nbytes;
}
#endif

#if HAVE_RW_STRING_DESC
# define sd_char_at(s, i) \
   ({typeof (s) _s_ = (s); \
     _sd_char_at (_s_._nbytes, _s_._data, i); \
   })
GL_STRING_DESC_INLINE char
_sd_char_at (idx_t s_nbytes, const char *s_data, idx_t i)
  _GL_ATTRIBUTE_NONNULL ((2));
GL_STRING_DESC_INLINE char
_sd_char_at (idx_t s_nbytes, const char *s_data, idx_t i)
{
  if (!(i >= 0 && i < s_nbytes))
    /* Invalid argument.  */
    abort ();
  return s_data[i];
}
#else
GL_STRING_DESC_INLINE char
sd_char_at (string_desc_t s, idx_t i)
{
  if (!(i >= 0 && i < s._nbytes))
    /* Invalid argument.  */
    abort ();
  return s._data[i];
}
#endif

#if HAVE_RW_STRING_DESC
# define sd_data(s) \
   _Generic (s, \
             string_desc_t    : (s)._data, \
             rw_string_desc_t : (s)._data)
#else
GL_STRING_DESC_INLINE const char *
sd_data (string_desc_t s)
{
  return s._data;
}
#endif

#if HAVE_RW_STRING_DESC
# define sd_is_empty(s) \
   _Generic (s, \
             string_desc_t    : (s)._nbytes == 0, \
             rw_string_desc_t : (s)._nbytes == 0)
#else
GL_STRING_DESC_INLINE bool
sd_is_empty (string_desc_t s)
{
  return s._nbytes == 0;
}
#endif

extern bool _sd_equals (idx_t a_nbytes, const char *a_data,
                        idx_t b_nbytes, const char *b_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_equals(a, b) \
   ({typeof (a) _a_ = (a); \
     typeof (b) _b_ = (b); \
     _sd_equals (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
   })
#else
GL_STRING_DESC_INLINE bool
sd_equals (string_desc_t a, string_desc_t b)
{
  return _sd_equals (a._nbytes, a._data, b._nbytes, b._data);
}
#endif

extern bool _sd_startswith (idx_t s_nbytes, const char *s_data,
                            idx_t prefix_nbytes, const char *prefix_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_startswith(s, prefix) \
   ({typeof (s) _s_ = (s); \
     typeof (prefix) _prefix_ = (prefix); \
     _sd_startswith (_s_._nbytes, _s_._data, _prefix_._nbytes, _prefix_._data); \
   })
#else
GL_STRING_DESC_INLINE bool
sd_startswith (string_desc_t s, string_desc_t prefix)
{
  return _sd_startswith (s._nbytes, s._data, prefix._nbytes, prefix._data);
}
#endif

extern bool _sd_endswith (idx_t s_nbytes, const char *s_data,
                          idx_t suffix_nbytes, const char *suffix_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_endswith(s, suffix) \
   ({typeof (s) _s_ = (s); \
     typeof (suffix) _suffix_ = (suffix); \
     _sd_endswith (_s_._nbytes, _s_._data, _suffix_._nbytes, _suffix_._data); \
   })
#else
GL_STRING_DESC_INLINE bool
sd_endswith (string_desc_t s, string_desc_t suffix)
{
  return _sd_endswith (s._nbytes, s._data, suffix._nbytes, suffix._data);
}
#endif

extern int _sd_cmp (idx_t a_nbytes, const char *a_data,
                    idx_t b_nbytes, const char *b_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_cmp(a, b) \
   ({typeof (a) _a_ = (a); \
     typeof (b) _b_ = (b); \
     _sd_cmp (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
   })
#else
GL_STRING_DESC_INLINE int
sd_cmp (string_desc_t a, string_desc_t b)
{
  return _sd_cmp (a._nbytes, a._data, b._nbytes, b._data);
}
#endif

extern int _sd_c_casecmp (idx_t a_nbytes, const char *a_data,
                          idx_t b_nbytes, const char *b_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_c_casecmp(a, b) \
   ({typeof (a) _a_ = (a); \
     typeof (b) _b_ = (b); \
     _sd_c_casecmp (_a_._nbytes, _a_._data, _b_._nbytes, _b_._data); \
   })
#else
GL_STRING_DESC_INLINE int
sd_c_casecmp (string_desc_t a, string_desc_t b)
{
  return _sd_c_casecmp (a._nbytes, a._data, b._nbytes, b._data);
}
#endif

extern ptrdiff_t _sd_index (idx_t s_nbytes, const char *s_data, char c)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_index(s, c) \
   ({typeof (s) _s_ = (s); \
     _sd_index (_s_._nbytes, _s_._data, c); \
   })
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_index (string_desc_t s, char c)
{
  return _sd_index (s._nbytes, s._data, c);
}
#endif

extern ptrdiff_t _sd_last_index (idx_t s_nbytes, const char *s_data, char c)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_last_index(s, c) \
   ({typeof (s) _s_ = (s); \
     _sd_last_index (_s_._nbytes, _s_._data, c); \
   })
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_last_index (string_desc_t s, char c)
{
  return _sd_last_index (s._nbytes, s._data, c);
}
#endif

extern ptrdiff_t _sd_contains (idx_t haystack_nbytes, const char *haystack_data,
                               idx_t needle_nbytes, const char *needle_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_contains(haystack, needle) \
   ({typeof (haystack) _haystack_ = (haystack); \
     typeof (needle) _needle_ = (needle); \
     _sd_contains (_haystack_._nbytes, _haystack_._data, \
                   _needle_._nbytes, _needle_._data); \
   })
#else
GL_STRING_DESC_INLINE ptrdiff_t
sd_contains (string_desc_t haystack, string_desc_t needle)
{
  return _sd_contains (haystack._nbytes, haystack._data,
                       needle._nbytes, needle._data);
}
#endif

extern string_desc_t _sd_new_addr (idx_t n, const char *addr)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
extern rw_string_desc_t _rwsd_new_addr (idx_t n, /*!*/const/*!*/ char *addr)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_new_addr(n, addr) \
   _Generic (addr, \
             const char * : _sd_new_addr (n, addr), \
             char * : /* Treat string literals like 'const char *'.  */ \
                      __builtin_choose_expr (__builtin_constant_p (addr), \
                                             _sd_new_addr (n, addr), \
                                             _rwsd_new_addr (n, addr)))
#else
# define sd_new_addr(n, addr) \
   _rwsd_new_addr (n, addr)
#endif

#if HAVE_RW_STRING_DESC
# define sd_substring(s, start, end) \
   ({typeof (s) _s_ = (s); \
     idx_t _start_ = (start); \
     idx_t _end_ = (end); \
     if (!(_start_ >= 0 && _start_ <= _end_ && _end_ <= _s_._nbytes)) \
       /* Invalid arguments.  */ \
       abort (); \
     typeof (s) _result_; \
     _result_._nbytes = _end_ - _start_; \
     _result_._data = _s_._data + _start_; \
     _result_; \
   })
#else
GL_STRING_DESC_INLINE string_desc_t
sd_substring (string_desc_t s, idx_t start, idx_t end)
{
  if (!(start >= 0 && start <= end && end <= s._nbytes))
    /* Invalid arguments.  */
    abort ();
  string_desc_t result;
  result._nbytes = end - start;
  result._data = s._data + start;
  return result;
}
#endif

extern int _sd_write (int fd, idx_t s_nbytes, const char *s_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_write(fd, s) \
   ({int _fd_ = (fd); \
     typeof (s) _s_ = (s); \
     _sd_write (_fd_, _s_._nbytes, _s_._data); \
   })
#else
GL_STRING_DESC_INLINE int
sd_write (int fd, string_desc_t s)
{
  return _sd_write (fd, s._nbytes, s._data);
}
#endif

extern int _sd_fwrite (FILE *fp, idx_t s_nbytes, const char *s_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_fwrite(fp, s) \
   ({FILE *_fp_ = (fp); \
     typeof (s) _s_ = (s); \
     _sd_fwrite (_fp_, _s_._nbytes, _s_._data); \
   })
#else
GL_STRING_DESC_INLINE int
sd_fwrite (FILE *fp, string_desc_t s)
{
  return _sd_fwrite (fp, s._nbytes, s._data);
}
#endif

extern int _sd_copy (rw_string_desc_t *resultp,
                     idx_t s_nbytes, const char *s_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (3, 2);
#if HAVE_RW_STRING_DESC
# define sd_copy(resultp, s) \
   ({rw_string_desc_t *_resultp_ = (resultp); \
     typeof (s) _s_ = (s); \
     _sd_copy (_resultp_, _s_._nbytes, _s_._data); \
   })
#else
_GL_ATTRIBUTE_NODISCARD GL_STRING_DESC_INLINE int
sd_copy (rw_string_desc_t *resultp, string_desc_t s)
{
  return _sd_copy (resultp, s._nbytes, s._data);
}
#endif

extern char *_sd_c (idx_t s_nbytes, const char *s_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (2, 1);
#if HAVE_RW_STRING_DESC
# define sd_c(s) \
   ({typeof (s) _s_ = (s); \
     _sd_c (_s_._nbytes, _s_._data); \
   })
#else
GL_STRING_DESC_INLINE char *
sd_c (string_desc_t s)
{
  return _sd_c (s._nbytes, s._data);
}
#endif

extern void _sd_overwrite (rw_string_desc_t s, idx_t start,
                           idx_t t_nbytes, const char *t_data)
  _GL_ATTRIBUTE_NONNULL_IF_NONZERO (4, 3);
#if HAVE_RW_STRING_DESC
# define sd_overwrite(s, start, t) \
   ({rw_string_desc_t _s_ = (s); \
     idx_t _start_ = (start); \
     typeof (t) _t_ = (t); \
     _sd_overwrite (_s_, _start_, _t_._nbytes, _t_._data); \
   })
#else
GL_STRING_DESC_INLINE void
sd_overwrite (rw_string_desc_t s, idx_t start, string_desc_t t)
{
  return _sd_overwrite (s, start, t._nbytes, t._data);
}
#endif


#ifdef __cplusplus
}
#endif

_GL_INLINE_HEADER_END


#endif /* _STRING_DESC_H */
