[C++] A find-Function to Append .and_then(..).or_else(..) in C++

I'm afraid I haven't had the time to dive into C++23 yet. But today I came across a LinkedIn post by Šimon Tóth, in which he gave an example of std::optional in C++23. In Short: C++23 introduced a monadic interface. This means that you can sort of append functions/lambdas. Just imagine a std::optional of any type, you can do:

// any optional
std::optional<int> any_opt;
// ... 
// i can call and_then on my optioanl and a 
// lambda (or callable) is executed. if the optional
// doesn't hold a value, the lambda isn't executed
any_opt.and_then([](int& value){/*do something...*/});

There are more functions to use. But this got me thinking...

Lately I have been working a lot with containers and find and find_if functions. Most of the time I have an unordered_map, but for the sake of simplicity I'll stick to a vector<int> in this article.

In my particular case, the value I want to find is not always guaranteed to exist. This means that I often use code like this:

// any existing vector
std::vector<int> v = {1,2,3,4};
// lets use std::find
auto it = std::find(v.begin(), v.end(), any_value);
// and we'll see if we found our value 
if (it == v.end()) {
    // do this
} else {
    // do that 
}

I honestly think that it will be okay if I don't use it too often in my code, but messes do grow fast, don't they? And this is what brought me to the idea of using the C++23 std::optional approach to find.

Note that this is a very simplified approach, there is still a lot of work to be done to make this kind of overload work for all available containers. It's more for me to get my thoughts down on paper (or in this case, on the screen).

namespace cwt 
{    
    // first we craete a type which will hold our find result
    template<typename T> 
    class find_result 
    {
        public: 
            find_result() = default;
            find_result(T value) : m_value{value} {}

            // this is the and_then method which gets a callback
            template<typename Func>
            const find_result<T>& and_then(const Func&& func) const
            {
                // we only call the callback if we have a value 
                if (m_value.has_value()) {
                    func(m_value.value());
                }
                // and to further add or_else we return *this
                return *this;
            }

            // almost same here, just with return type void
            template<typename Func>
            void or_else(const Func&& func) const
            {
                if (!m_value.has_value()) {
                    func();
                }
            }
        private:
            // and since we don't know if we found a value
            // we hold possible one as optional
            std::optional<T> m_value{std::nullopt}; 
    };

    // this my find function, where try to find a value in a vector
    template<typename T>
    find_result<T> find(const std::vector<T>& v, const T value) 
    {
        // like before we use the iterator
        auto it = std::find(v.begin(), v.end(), value);
        // and if we didn't find the value we return
        // find_result default constructed
        if (it == v.end()) {
            return find_result<T>();
        } else {
            // or with the found value
            return find_result<T>(*it);
        }
    }
} // namespace cwt

int main() 
{
    // lets create a simple vector of int values
    std::vector<int> v = {1,2,3,4};

    // we use our find function
    // and since we return find_result<int> 
    // we can append or call and_then or_else directly
    cwt::find(v, 2)
    .and_then([](int result){ std::cout << "found " << result << '\n'; })
    .or_else([](){ std::cout << "found nothing\n"; })
    ;

    cwt::find(v, 10)
    .and_then([](int result){ std::cout << "found " << result << '\n'; })
    .or_else([](){ std::cout << "found nothing\n"; })
    ;

    return 0;
}

Conclusion

Find this example here on compiler explorer.

I couldn't find anything existing about this or a simplified solution to find. If somebody has another solution or knows something existing, please let me know and leave a comment.

I really liked the C++23 std::optional, so these are just some initial thoughts. I'm working on a controller and I don't have C++23 available at the moment. So this would be a workaround for C++17/20. If I had C++23 then I could just return a std::optional from find to have the same behavior.

I think this might make my code a little better in a couple of places. I know it's still a lot of work to have the full proofing solution for all containers, more accessors and so on and so forth. But it's an idea and maybe it will grow.

Have a nice day.

Best Thomas

Previous
Previous

[C++] Exploring the Potential About a std::find Wrapper

Next
Next

[C++] Breaking Dependencies With Templates