0

The one annoyance I have with the std::string class is that the only ways (that I could figure out after reading cplusplus.com's info about it) to get the location of the last character in the string is (stringVariable.length() - 1) or stringVariable.rfind(stringVariable.back()). It'd be nice if I could just do stringVariable.last(). This is for a function I wrote in a personal project that needs to reference this number of a user's input string.

Some ideas I had were creating a struct that is just a string and has the last() function as a member function, or a class that copies the string class and adds that member. With a struct, I'm not sure how (or if it's possible) to make it so I can make the variable assigned to the type automatically reference the string in it so that I don't need to make a constant member call. I'm taking the second C++ class in school right now, but we haven't touched on C++ classes yet.

I tried reading some guides and answers here about classes, and they don't look all that different from structs, but the more complex stuff in either case was Greek to me. I only learned how to use a struct as a holder for multiple variable types; like the name string and id int for a person, or something like that. I learned from the guides I can put functions in them, and define operator behavior too, but when I tried to define operator behavior for the string struct I made, I couldn't get it to work.

Based on the example I read, I tried:

str &operator=(const str &ing)
{
    s = ing.s;
    return s;
}

But when I tried to test it by using = to copy a str variable to a string variable I could cout, it errors that str can't be converted to string, so I tried a few adjustments, and after getting errors about the & and such, I ended up with:

string operator=(str ing)
{
    return ing.s;
}

Which gets the same error. Here's the full struct test program I'm using:

#include <iostream>
#include <string>
using namespace std;

struct str
{
    string s;

    string operator=(str ing)
    {
        return ing.s;
    }

    int last()
    {
        return s.length() - 1;
    }
};

int main()
{
    str ing {"Hello World!"};
    string i = ing;
    cout << i;

    return 0;
}

If I could get the = assignment to work, I'd try to get << and such to work with str, too.

If that doesn't turn out to be possible, my fallback is just using a function like:

int last(string str)
{
    return str.length() - 1;
}

Edit: For More Info

I was asked many times in the comments what my actual need is for this. In truth it’s just that it annoys me that it doesn’t have that member function. I’m surprised it wasn’t implemented long ago.

Thanks to the commenters I was able to come up with the diabolical workaround of overloading an operator to make a string able to subtract a function to output the result of a string being input to the function. I used - because I was thinking script notation: stringVariable -l or stringVariable -last. Then I can also just use #define to remove the need for the operator, making it stringVariable last() (“last()” instead of “last” because aesthetic).

Another option I figured out was reversing positions and using #define to make it last stringVariable, or #define in - to make it last in stringVariable. Although the simplest version of the operator function for this by far is just defining a negative string as the int output directly. Not that any of that is really better than just using the function normally, but I enjoyed learning about those features of C++.

The single answerer also consolidated much of the commenters advice and brought it to the next level to give me a more elegant version of the idea I had to create my own class with a string that could do all the string stuff and the extra member. However taking the advice of reexamining my needs I think perhaps ‘string’ isn’t the only option I have for a user input container. I definitely need the int output made by stringVariable.size() - 1 with whatever char array I use (since it’s used in mathematical equations), but maybe a vector or something else could do that already?

I was briefly told about vectors in my previous class, but we weren’t allowed to use them. Since this is for a simulated environment I’m doing on my own (that I might use for the basis of a game some day) I can use anything I want though. I also decided to pull an allnighter last night and just started reading about containers at random on cplusplus.com.

So I’m wondering if perhaps a list or deque could work too. I wasn’t really taught about anything other than string and hardcoded arrays (yet) though. I use the string to store the user’s input to simplify the process of avoiding unforeseeable crashing/errors that come from bad user inputs, then just translate the strings to whatever I need with various functions I've written.

Pål Hart
  • 19
  • 6
  • sooo `stringVariable.end()`? `get the location of the last character in the string` What do you mean by "location"? – KamilCuk Sep 28 '21 at 23:23
  • 2
    Is there some reason you can't use iterators? And can you explain why `str.length() - 1` (or slightly shorter, `str.size() - 1`) isn't acceptable? This question gets kinda fuzzy in the middle; I'm not sure how the first two paragraphs and the last code sample relate to anything in the middle. "Location" of the last character is pretty fuzzy too; the index of the last character? An iterator pointing to the last character? A raw pointer that dereferences to the last character? – ShadowRanger Sep 28 '21 at 23:25
  • 1
    For the assignment what about a conversion operator: https://onlinegdb.com/c36q2ThqU – Jerry Jeremiah Sep 28 '21 at 23:25
  • 2
    To answer the question from your title, [no, there is no sane way to monkey-patch an existing type in C++](https://stackoverflow.com/q/1584907/364696). – ShadowRanger Sep 28 '21 at 23:28
  • 2
    A free function is the normal way to extend classes in C++: `last_index(str)`. (Fun fact, this exact function is `thing.lastIndex` in Kotlin, so there is precedent for that name.) – chris Sep 28 '21 at 23:36
  • @ShadowRanger, I can offer that a well-named function for this can sometimes reduce the chance of running into off-by-one errors, "one of the hard problems in computer science". – chris Sep 28 '21 at 23:40
  • 1
    You could make an [extension method](https://stackoverflow.com/a/57081233/4641116). But please don't do that, because your coworkers will have a hard time disposing of your body. – Eljay Sep 28 '21 at 23:41
  • 2
    Beware [XY problems](https://en.wikipedia.org/wiki/XY_problem). You have already concluded that the "fix" to your situation is to make changes to `std::string` instead of making them to how you use your string. This might rule out better solutions for your real goal. – JaMiT Sep 28 '21 at 23:50
  • 2
    There is no way to add member functions to a standard class (in the sense of the class being specified by the standard) in C++. Not only is a non-member function the normal way to extend functionality of a class in C++, it is a *recommended practice* (e.g. by authors such as Scott Meyers in his "Effective C++" series) over adding member functions. – Peter Sep 28 '21 at 23:56
  • 1
    You still haven't really explained _why_ it's so important to have this function. It kinda goes against all paradigms in C++ (and C, for that matter) dealing with strings. You're always going to see "length - 1" in code that accesses the index of the last character in the string. Why hide this in an extra layer? It just makes your program harder to follow when read by any experienced developer. Not only that, you've introduced an implicit typecast by assuming the index is of type `int`. – paddy Sep 28 '21 at 23:58
  • And each time you call `last`, you need to ensure the string is not empty... Unless you are in a domain where you really do a lot of processing on strings, I would simply use indexing (`str[str.length() - 1]`) or iterator (`*--str.end()`). If you still want utilities functions them put free functions in a namespace : `namespace strutils { char last(const std::string &); }` – Phil1970 Sep 29 '21 at 00:44

1 Answers1

0

creating a struct that is just a string and has the last() function as a member function

That will work just fine, if you don't mind implementing the interfaces needed to convert between std::string <-> str.

Also, be aware that std::string indexes use std::string::size_type, which is not int. size_type is an unsigned type, typically std::size_t. std::string defines an npos constant to indicate "no index", that is what your last() function should return if the std::string is empty, eg:

std::string::size_type last_index() const
{
    return !s.empty() ? s.size() - 1 : std::string::npos;
}

But, if you really want to have last() return an int (ie, your std::string will never exceed 2147483647 characters):

int last_index() const
{
    return static_cast<int>(s.size()) - 1;
}

With a struct, I'm not sure how (or if it's possible) to make it so I can make the variable assigned to the type automatically reference the string in it so that I don’t need to make a constant member call.

In short, no.

Based on the example I read, I tried:

str &operator=(const str &ing)
{
    s = ing.s;
    return s;
}

It needs to return a reference to the str object that is being assigned to, not the std::string member that was actually modified, eg:

str& operator=(const str &rhs)
{
    s = rhs.s;
    return *this;
}

But when I tried to test it by using = to copy a str variable to a string variable I could cout, it errors that str can't be converted to string

Correct, it can't by default. You need to define a conversion operator for that purpose, eg:

operator std::string() const
{
    return s;
}

If I could get the = assignment to work, I'd try to get << and such to work with str, too.

Yes, you should, eg:

std::ostream& operator<<(std::ostream &out, const str &rhs)
{
    return out << rhs.s;
}

If that doesn't turn out to be possible, my fallback is just using a function

That is a good idea, eg:

std::string::size_type last_index(const std::string &s)
{
    return !s.empty() ? s.size() - 1 : std::string::npos;
}

Or:

int last_index(const str::string &s)
{
    return static_cast<int>(s.size()) - 1;
}

Here's the full struct test program I'm using

Try this:

#include <iostream>
#include <string>
using namespace std;

class str
{
private:
    string s;

public:
    str() = default;
    str(const string &s) : s(s) {}

    str& operator=(const str &rhs)
    {
        s = rhs.s;
        return *this;
    }

    operator string() const
    {
        return s;
    }

    string::size_type last_index() const
    {
        return !s.empty() ? s.size() - 1 : string::npos;
    }

    friend ostream& operator<<(ostream &out, const str &rhs)
    {
        return out << rhs.s;
    }
};

string::size_type last_index(const string &s)
{
    return !s.empty() ? s.size() - 1 : string::npos;
}

int main()
{
    str ing {"Hello World!"};
    cout << ing << endl;
    cout << ing.last_index() << endl;

    string i = ing;
    cout << i << endl;
    cout << last_index(i) << endl;

    return 0;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770