1

Let's say I have a function:

t_list *get_first_matching(t_list *head_of_list, <some other stuff>);

This function would retrieve an element from the given list and I would be able to modify it.

But if I have something like this:

void print_matching(const t_list *head_of_list)
{
    const matching_elemnt = get_first_matching(head_of_list, <other stuff>);
    <print the element>
}

The compiler will say that that I can't pass a constant pointer to that function.

What I could do, is copy paste that function, adding const:

const t_list *get_first_matching(const t_list *head_of_list, <some other stuff>);

Can I make only one function that would work with const parameters and would also work in case I would need to modify the retrieved element?

Is there a solution without warnings or without const cast?

Emil Terman
  • 526
  • 4
  • 22

3 Answers3

4

2 approaches:

  1. Form a t_list *get(const t_list *, ...) much like standard C functions char *strstr(const char *s1, ...); with a similar issue. Even though s1 points to const data and the value returned is a pointer to an element in s1, the function returns a non-const pointer. This is common practice in C.

  2. Form 2 getter functions. It is easy to make one a wrapper of the other.

    const t_list *get_const(const t_list *, ...) {
      ...
    } 
    
    t_list *get(t_list *, ...) {
      return (t_list *) get_const(t_list *, ...);
    }
    

I prefer #2. As const did not appear in original C, solution #1 was the best way to wedge const into existing code. Yet OP's is making new code and so the #2 option is available.


A 3rd sample C11 approach: use _Generic.
_Generic selects which function to use. This approach must be taken with caution. The fine details of _Generic still need work, IMO. The "hiding" of functions is disconcerting for some and _Generic is easy to employ incorrectly. Note the getnext(s) example below evaluates s twice, but once is with _Generic(s), a compile time evaluation.

char *getnext_nonconst(char *s) {
  return s + 1;
}

const char *getnext_const(const char *s) {
  return s + 1;
}

#define getnext(s) _Generic((s), \
    char *: getnext_nonconst, \
    const char *: getnext_const \
    ) (s)

int main(void) {
  char *ncs = (char []){"chux is amazing"};
  ncs = getnext(ncs);  // getnext_nonconst() is called
  const char *cs = "but not perfect";
  cs = getnext(cs);    // getnext_const() is called
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • now that you mention it, strstr() really casts `const char*` into `char*`, I thought that's bad practice – Emil Terman Dec 29 '17 at 15:34
  • 2
    @EmilTerman How `strstr()` "does it" with a cast or union or who knows what, is an implementation detail that is not a concern. What is important is that the returned pointer points into the passed in `s1` and should be treated the same. C++ fixes this by overloading `strstr()` with multiple functions. We could do this in C99 with one "function" using macro magic and `_Genric()`. – chux - Reinstate Monica Dec 29 '17 at 15:41
  • #1 approach also used in `strto....()`, `bsearch()`, `memchr()`, `strchr()`, `strrchr()`, many others. – chux - Reinstate Monica Dec 29 '17 at 15:46
  • 1
    @EmilTerman The "bad practice" is casting away `const` when it is not absolutely known that it is OK to do so. In the case of `t_list *get(t_list *, ...)`, code "knows" it is OK to cast away `const`. – chux - Reinstate Monica Dec 29 '17 at 15:55
  • 1
    @Stargateur a bit of French humor, non? – chux - Reinstate Monica Dec 29 '17 at 16:42
3

Change your existing function to accept a const parameter:

t_list *get_first_matching(const t_list *head_of_list, <some other stuff>);

This is the only version you need. When you call this with a non-const parameter, it will be implicitly converted to const.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
  • I wouldn't be able to modify the retrieved element without a cast to const. – Emil Terman Dec 29 '17 at 14:53
  • @EmilTerman So you are saying that your question is missing some important details. Please edit your question to provide these. – Code-Apprentice Dec 29 '17 at 14:55
  • c doesn't allow that – Emil Terman Dec 29 '17 at 15:01
  • @EmilTerman C doesn't allow what? You need to show more code. Your original question does not show anything about your list data structure. Nothing from your original questions shows why you cannot just return `t_list*` while accepting a `const t_list*` parameter. – Code-Apprentice Dec 29 '17 at 15:02
  • for example this will compile with a warning: https://gist.github.com/TermanEmil/c2bc73e1d13ec126a5a68479f70d1f82 – Emil Terman Dec 29 '17 at 15:09
  • @EmilTerman C allow asm... C allow everything but doesn't guaranty that will do what you want. – Stargateur Dec 29 '17 at 15:09
  • Is worth asking why you're motivated to return to the caller the same list that was passed as an argument. Without dot notation, chaining functions like in JavaScript won't work. The caller already knows the pointer passed in, I don't see why it would have to be returned. If they're in fact different, as looks like it would be the case from your declaration, then they don't have to match in constancy. Lol, never used that work on SO before ;) – erik258 Dec 29 '17 at 15:17
  • @DanFarrell I'm sorry, I didn't get that – Emil Terman Dec 29 '17 at 15:23
  • 1
    Non const to const is safe and is only bad practice to do explicitly because it can happen automatically. Const to non const breaks the const contract and turns the value of const into misinformation. – erik258 Dec 29 '17 at 15:29
  • 1
    @DanFarrell By the name get_first_matching(), does some kind off search which potentially can return a pointer to the first element. However, I find it difficult to believe that it will simply return the given parameter directly. This is heavily dependent on missing implementation details. – Code-Apprentice Dec 29 '17 at 15:37
2

You could (in practice) cast the first argument, e.g.

matching_elemnt = get_first_matching((const t_list *)head_of_list, other stuff );

However, the get_first_matching function has (if it is a genuine getter function) probably the wrong declaration and definition. I feel its first argument should be const t_list* but you need to decide (and perhaps correct your code, even at all the call sites)...

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • Isn't it bad practice to cast into const? – Emil Terman Dec 29 '17 at 14:50
  • 1
    @EmilTerman I think it could trigger some UB to do that. I will never advice to break const by casting. Note that I don't understand why Basile cast to const and not cast a const to a non const... – Stargateur Dec 29 '17 at 14:53
  • Adding const makes a non const pointer const, de-escalating permissions and maintaining the non const contract (which allows but doesn't require changes). Removing const breaks the contract by adding change permission. Permission isn't exactly the right word, but the analogy is clear. – erik258 Dec 29 '17 at 15:14