patterncppMinor
Providing const- and non-const overloads of a strstr() implementation
Viewed 0 times
nonprovidingconststrstrandimplementationoverloads
Problem
In a question about reimplementing
Implementation A: const version that chains to a non-const version
Implementation B: template
Implementation C: template with char_traits
```
template
struct char_traits {
typedef T type;
typedef const T const_type;
};
template
static typename char_traits::type *strstr(
typename char_traits::type *input,
typename char_traits::const_type *find) {
do {
typename char_traits::const_type p, q;
for (p = input, q = find; q != '\0' && p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
// Explicit instantiation doesn't help with overloading?
// http://stackoverflow.com/a/8061522/1157100
//
// template char strstr(char input, const char *find);
// template char strstr(char input, const char *find);
// So it seems that chaining is still necessary?
const char strstr(const char input, const char *find) {
return strstr(input, find);
}
char strstr(char input,
strstr(), I posted an answer. I pointed out a need for const- and non-const versions of the function, and provided Implementation A below, which received criticism about the ugliness of the const_cast. Therefore, I've written two more implementations (B and C).Implementation A: const version that chains to a non-const version
char *strstr(char *input, const char *find) {
do {
const char *p, *q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
const char *strstr(const char *input, const char *find) {
return strstr(const_cast(input), find);
}Implementation B: template
template
T *strstr(T *input, const T *find) {
do {
const T *p, *q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}Implementation C: template with char_traits
```
template
struct char_traits {
typedef T type;
typedef const T const_type;
};
template
static typename char_traits::type *strstr(
typename char_traits::type *input,
typename char_traits::const_type *find) {
do {
typename char_traits::const_type p, q;
for (p = input, q = find; q != '\0' && p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
// Explicit instantiation doesn't help with overloading?
// http://stackoverflow.com/a/8061522/1157100
//
// template char strstr(char input, const char *find);
// template char strstr(char input, const char *find);
// So it seems that chaining is still necessary?
const char strstr(const char input, const char *find) {
return strstr(input, find);
}
char strstr(char input,
Solution
I don't like casting away const on a const object.
This opens you up to the possibility of maintenance errors.
Here you are assuming that the other version of
I would just do it the other way around:
OK. So we need to add another cast. But in this version we protect ourselves from errors. If there is an error in the algorithm (were input is modified) then the compiler will now be able to detect it.
The const_cast<> to remove const(ness) is in a safe place returning the pointer to its original state (after there is const_cast<> to add const (needed so we can differentiate the two functions).
Personally I would take it a step further (so we can remove one of the const_casts). By making the
Don't like
Same for
This opens you up to the possibility of maintenance errors.
const char *strstr(const char *input, const char *find)
{
return strstr(const_cast(input), find);
}Here you are assuming that the other version of
strstr() will not attempt to mangle the input. This may be fair to assume in the original version, but what if somebody makes a mistake while updating to use a better algorithm. The compiler will not detect the problem and you are left with UB.I would just do it the other way around:
char const* strstr(char const* input, char const* find)
{
do {
char const* p;
char const* q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
char* strstr(char* input, char const* find)
{
return const_cast(strstr(const_cast(input), find));
}OK. So we need to add another cast. But in this version we protect ourselves from errors. If there is an error in the algorithm (were input is modified) then the compiler will now be able to detect it.
The const_cast<> to remove const(ness) is in a safe place returning the pointer to its original state (after there is const_cast<> to add const (needed so we can differentiate the two functions).
Personally I would take it a step further (so we can remove one of the const_casts). By making the
strstr() just wrapper functions around the function that does the work.char const* strstrDo(char const* input, char const* find)
{
do {
char const* p;
char const* q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
// Interface for backwards compatibility with old C code
// that does not handle const correctness well.
char* strstr(char* input, char const* find)
{
// It is safe to cast away the const(ness) here.
// The result of `strstrDo()` is a location inside
// `input` (which we already know is non const) or
// it is NULL (where the type is less relevant).
return const_cast(strstrDo(input, find));
}
// Correct interface.
char const* strstr(char const* input, char const* find)
{
return strstrDo(input, find);
}Don't like
Implementation B: template because it has the same problems in that errors (in modification input are not detected by the compiler (potentially)).Same for
Implementation C: template with char_traits. But that goes way over the top. I don't actually see the need for the traits on such a simple function.Code Snippets
const char *strstr(const char *input, const char *find)
{
return strstr(const_cast<char *>(input), find);
}char const* strstr(char const* input, char const* find)
{
do {
char const* p;
char const* q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
char* strstr(char* input, char const* find)
{
return const_cast<char*>(strstr(const_cast<char const*>(input), find));
}char const* strstrDo(char const* input, char const* find)
{
do {
char const* p;
char const* q;
for (p = input, q = find; *q != '\0' && *p == *q; p++, q++) {
}
if (*q == '\0') {
return input;
}
} while (*(input++) != '\0');
return NULL;
}
// Interface for backwards compatibility with old C code
// that does not handle const correctness well.
char* strstr(char* input, char const* find)
{
// It is safe to cast away the const(ness) here.
// The result of `strstrDo()` is a location inside
// `input` (which we already know is non const) or
// it is NULL (where the type is less relevant).
return const_cast<char*>(strstrDo(input, find));
}
// Correct interface.
char const* strstr(char const* input, char const* find)
{
return strstrDo(input, find);
}Context
StackExchange Code Review Q#54155, answer score: 2
Revisions (0)
No revisions yet.