2

Sometimes using pure-C libs in my C++ projects, I see strange (in my opinion) function declarations.

E.g.: libldap's ldap_search_ext(): https://linux.die.net/man/3/ldap_search_ext_s

int ldap_search_ext(
       LDAP *ld,
       char *base,
       int scope,
       char *filter,
       char *attrs[], // this one!
       int attrsonly,
       LDAPControl **serverctrls,
       LDAPControl **clientctrls,
       struct timeval *timeout,
       int sizelimit,
       int *msgidp );

Why can't attrs[] be a const char *?

Declarations like this don't want to change the content of the pointer and generate a lot of issues:

// pure C
void func(char * data[])
{
  ...
}

func({"blabla"}); // won't work (corrected: yes, this is wrong syntax, but it's true for structs of pointers)

const char *d[] = {"blabla", "blablu"};
func(d); // won't work

// C++
const std::string str("blabla");
char * data[] = { str.data() }; // even non-const won't work (because data() returns const*)
/// etc...

Is there any reason for not declaring such arguments as const?

Boann
  • 48,794
  • 16
  • 117
  • 146
  • 7
    Added `C++` (rare case where both `C` & `C++` tags are appropriately used in same question.) – ryyker May 31 '21 at 16:53
  • 3
    OpenLDAP is a very old, established, library. It was written back in the days where `const`-correctness was not a de-facto requirement. In this case, it *should* be a `const` pointer. – Sam Varshavchik May 31 '21 at 16:54
  • 2
    `char *attrs[]` is essentially the same thing as `char **attrs`, and `const char**` does not behave like you'd intuitively expect, so it might have something to do with that. –  May 31 '21 at 16:55
  • Fwiw, `const d = {"blabla", "blablu"};` won't work regardless (yes, I have implicit-int disabled). Regardless, in remotely modern code there is *zero* reason for not properly const-izing pointers of *any* type when the pointed to data is considered immutable. – WhozCraig May 31 '21 at 17:00
  • BTW, `func({"blabla"});` is not pure C, because you would need a compound literal there. – mediocrevegetable1 May 31 '21 at 17:00
  • Being a little bit more pedantic, the correct definition would actually be `char const * const *attrs` – Marco Bonelli May 31 '21 at 17:01
  • of course I meant `const char *d[] = {"blabla", "blablu"};` this should work since C99 if I remember correctly.. Haven't written in C since years.. – Vitalii Kolmakov May 31 '21 at 17:03
  • Lookup `ritchie strchr` for a bit of a background on ways in which const is a poor concept in C. Probably in C++ too; ref `thompson c++`. Notably, the plan9 C compiler includes a default equivalent to '-Dconst=' to alleviate the problems. – mevets May 31 '21 at 17:39
  • read this: https://stackoverflow.com/questions/8908071/const-correctness-in-c-vs-c –  May 31 '21 at 18:17

2 Answers2

5

This is mostly just (due to) a historical wart in the C standard that has never been fixed.

When const was added to C, the ability to implictly (and safely) convert simple pointers was added -- you can implicitly convert a T * to a const T * just fine. However, (safe) conversion of more complex pointer types was missed -- you can't convert a T * const * to a const T * const *. As a result, when a library takes a double pointer like this, it does not have any 'good' way of making it const if it is read only. Making it either a const char ** or a const char * const * would break some uses (requiring messy explicit casts).

Note that allowing implicit conversions of T ** to const T ** would be unsafe -- such a pointer could be used to modify a T * to point at a const T * without a cast.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
4

One likely reason is that string constants in C (unlike C++) are not const.

As such, there was a historical lack of const-correctness in many libraries that should have been there. Had string constants been const, programmers would have been forced to account for it.

When dealing with libraries such as this that you know don't modify the argument, you have to apply a const_cast to make it fit.

char *d[] = {const_cast<char *>("blabla"), const_cast<char *>("blablu")};
func(d);

const std::string str("blabla");
char * data[] = { const_cast<char *>(str.data()) };
func(data);
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Thanks, sure. I always use this solution, My question was more about the reasons of the libs developers. I've also seen it in STM Library and always interpreted it as low code quality. – Vitalii Kolmakov May 31 '21 at 17:22