Search code examples
c++templatesc++17variadic-templates

Capture return value from fold expression in std::apply


I have a simple snippet that tries to illustrate what I'm trying to do. I pack a few class instances in a tuple and iterate over them using std::apply. I'd like to capture two values:

  1. If a matching key was found
  2. If a key was found, return the result of the do_test call

I capture (2) in a variable fn_ret, but for (1), I can't tell if a key was found while iterating over the tuple. I think I might need to store/get the result of search_tuple(args) somehow before passing it in the fold expression. How do I go about doing that?

#include <iostream>
#include <tuple>

using namespace std;

class foo
{
    public:
    foo(int i) : key_(i){}
    
    int get_key()
    {
        return key_;
    }

    int do_test()
    {
        std::cout << "ran test for key=" << key_ << "\n";
        return -1;
    }
    private:
    int key_;
};

int main()
{
    std::tuple tup { foo{1}, foo{2}, foo{3}, foo{4}, foo{5}, foo{6}, foo{7}, foo{8} };
    int key = 4;
    int fn_ret = 0;
    std::apply
        (
            [key, &fn_ret](auto&... args)
            {
                auto search_tuple = [key, &fn_ret](auto& x) {
                    int foo_key = x.get_key();
                    cout << "current key=" << foo_key << "\n";
                    if (foo_key == key)
                    {
                        std::cout << "found matching key, running test!\n";
                        fn_ret = x.do_test();
                        return 0;
                    }
                    return -1;
                };
                (search_tuple(args) && ...);
            }, tup
        );
    return 0;
}

Solution

  • The following code works to a reasonable extent. It stores a special initial value in fn_ret to check whether the key was found. Instead an additional boolean variable could be passed by reference from main along with key and fn_ret.

    #include <iostream>
    #include <tuple>
    
    using namespace std;
    
    class foo
    {
        public:
        foo(int i) : key_(i){}
        
        int get_key()
        {
            return key_;
        }
    
        int do_test()
        {
            std::cout << "ran test for key=" << key_ << "\n";
            return key_*key_; // Some operation on the key
        }
        private:
        int key_;
    };
    
    int main()
    {
        std::tuple tup { foo{1}, foo{2}, foo{3}, foo{4}, foo{5}, foo{6}, foo{7}, foo{8} };
        int key = 10;
        int fn_ret = -1;
        bool key_fnd = false;
        std::apply
            (
                [key, &fn_ret, &key_fnd](auto&... args) {
                    auto search_tuple = [key, &fn_ret, &key_fnd](auto& x) {
                        int foo_key = x.get_key();
                        cout << "current key=" << foo_key << "\n";
                        if (foo_key == key)
                        {
                            std::cout << "found matching key, running test!\n";
                            key_fnd = true;
                            fn_ret = x.do_test();
                            return 0;
                        }
                        return -1;
                    };
                    (search_tuple(args) && ...);
                }, tup
            );
        if (!key_fnd) {
          std::cout<<"The key was not found"<<std::endl;
        }
        else {
          std::cout<<"The result of running the test on key "<<key;
          std::cout<<" is "<<fn_ret<<std::endl;
        }
    
        return 0;
    }
    

    As per 康桓瑋's comment, the logic can be simplified as shown below. Note, the fold expression should be based on || to ensure that the key matches with any one of the keys. Also, the evaluation of the fold expression stops when a match has been found.

    bool key_fnd = std::apply(
                    [key, &fn_ret](auto&... args) {
                        auto search_tuple = [key, &fn_ret](auto& x) {
                            int foo_key = x.get_key();
                            cout << "current key=" << foo_key << "\n";
                            if (foo_key == key) {
                                std::cout << "found matching key, running test!\n";
                                fn_ret = x.do_test();
                                return true;
                            }
                            return false;
                        };
                        return (search_tuple(args) || ...);
                    }, tup
                );