Search code examples
c++c++20

How do I use templates to access data members of a struct?


I'm trying to summon an elder demon, and this is one of the trials before me.

I want a class which will take, as a template parameter, or at least a construction variable, the address to a data member of a class. I will later provide an instance of that class so the template class knows where to perform a small aspect of the summoning.

This is what I have so far, but wrong results:

#include <functional>
#include <iostream>

struct B
{
    int foo;
    bool bar;
};

template<auto M>
class A
{
public:
    typedef std::function<bool(decltype(std::declval<B>().*M)&)> Callback;

    A(Callback callback)
    :
    m_callback(callback)
    {}

    bool X
    (
        B& b
    ) const
    {
        if(m_callback(m_cache))
        {
            b.*M = m_cache;
            return true;
        }

        return false;
    }

protected:
    Callback m_callback;
    mutable typename std::remove_cvref<decltype(std::declval<B>().*M)>::type m_cache;
};

using FooA = A<&B::foo>;
using BarA = A<&B::bar>;

int main()
{
    FooA fooA([](int& f) { std::cout << "foo " << f << std::endl; ++f; return true; });
    BarA barA([](bool& b) { std::cout << "bar " << b << std::endl; b = !b; return true; });
    
    B b{.foo = 1, .bar = false};
    
    fooA.X(b);
    barA.X(b);
    fooA.X(b);
    barA.X(b);
    
    return 0;
};

output:

foo 0
bar 120
foo 1
bar 121

Solution

  • As @Miles Budnek noticed, m_cache is not initialized which explains why you get wrong results (note that valgrind is your friend for tracking such an issue).

    You can also define a shortcut type member_t that makes the whole thing easier to read:

    #include <functional>
    #include <iostream>
    
    struct B
    {
        int foo;
        bool bar;
    };
    
    template<auto M>
    class A
    {
    public:
    
        using member_t = std::remove_cvref_t<decltype(std::declval<B>().*M)>;
        
        typedef std::function<bool(member_t&)> Callback;
    
        A(Callback callback) : m_callback(callback) {}
    
        bool X (B& b) const
        {
            if(m_callback(m_cache))
            {
                b.*M = m_cache;
                return true;
            }
    
            return false;
        }
    
    protected:
        Callback m_callback;
        mutable member_t m_cache = {};  // initialization
    };
    
    using FooA = A<&B::foo>;
    using BarA = A<&B::bar>;
    
    int main()
    {
        FooA fooA([](int& f)  { std::cout << "foo " << f << std::endl; ++f;    return true; });
        BarA barA([](bool& b) { std::cout << "bar " << b << std::endl; b = !b; return true; });
        
        B b{.foo = 1, .bar = false};
        
        fooA.X(b);
        barA.X(b);
        fooA.X(b);
        barA.X(b);
        
        return 0;
    };
    

    Note: since you need std::remove_cvref_t, you should use the tag c++20 for your post.