αλεχολυτ
αλεχολυτ

Reputation: 5039

Generic function that accepts const and non-const data and related function

To make more sence for the question title consider the following code:

template <typename T>
void write(const T& /*data*/)
{
    // use data as const
}

template <typename T>
void read(T& /*data*/)
{
    // modify data
}

struct S { 
    int a;
    int b;
    // a lot of primitive types here
};

void output(const S& s)
{
    write(s.a);
    write(s.b);
    // ...
}

void input(S& s)
{
    read(s.a);
    read(s.b);
    // ...
}

int main()
{
    S s;
    input(s);
    output(s);
}

I have write and read functions that manipulate primitive data like int, double etc. I have several struct types like S that contains a lot of primitive data members, and I need to input and output such types via corresonding functions input and output (there are many of overloads for distinct types). As you can see the content of output and input functions mostly the same, only the inner functions and type constness are different. I would like to make some generic (template) function use to eliminate code duplicates from input/output functions.

I think the template signature will be the following (but I can miss something):

template <typename F, typename T>
void use(T& t) { ... }

So I could call the use function from input/output like this:

use<write, S>(s); // in output function
use<read, S>(s);  // in input fuction

How to archive this (or similar) behaviour to eliminate code duplications? I'm using C++14.

Upvotes: 2

Views: 265

Answers (3)

Vittorio Romeo
Vittorio Romeo

Reputation: 93324

template <typename S, typename F>
void use(S&& s, F&& f)
{
    f(s.a);
    f(s.b);
    f(s.c);
}

Usage:

use(s, [](const auto& x){ write(x); });
use(s, [](auto& x)      { read(x); });

live example on wandbox.org


If you need multiple types:

template <typename S, typename Target>
using EnableFor = std::enable_if_t<std::is_same_v<std::decay_t<S>, Target>>;

template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S0>
{
    f(s.a);
    f(s.b);
    f(s.c);
}

template <typename S, typename F>
auto use(S&& s, F&& f) -> EnableFor<S, S1>
{
    f(s.d);
    f(s.e);
    f(s.f);
}

Usage:

int main()
{
    S0 s0; 
    use(s0, [](const auto& x){ write(x); });
    use(s0, [](auto& x)      { read(x); });

    S1 s1;
    use(s1, [](const auto& x){ write(x); });
    use(s1, [](auto& x)      { read(x); });
}

live example on wandbox.org

Upvotes: 1

ALX23z
ALX23z

Reputation: 4713

I believe I understood what you want, but it is impossible. Currently there is no way to implement in C++ generic streaming for all classes. One would need access to information like number of fields the class has, their relative address as well as type in compile time.

Currently they are working on adding this to C++ for many years, seek Reflection TS. As far as I know it won't be ready for C++20.

However, you can organize your code so that each read/write method is easy to implement. For example:

class A : public B, public C
{
   private:
   vector<int> m_v;
   int m_x;
};

Then implement write for it as

template<>
void write(const A& a)
{
     write((const B&)a);
     write((const C&)a);
     write(a.m_v);
     write(a.m_x);
}

While for common data structures like std::vector<T> implement write via write<T>. It takes time but making it technical and using redirections can save a lot of code and time.

(You need to make write a friend function, or a member function to let it access private members)

Upvotes: 0

llllllllll
llllllllll

Reputation: 16424

You can define an interface of the template being used:

template<class M, class S>
void use(S &&s) {
    M::method(s.a);
    M::method(s.b);
};

Then implement it:

template <typename T>
void write(const T& /*data*/)
{
    // use data as const
}

struct writer {
    template <typename T>
    static void method(const T& t)
    {
        write(t);
    }
};

Now you can use it:

use<writer>(s);

For read, it's the same:

struct reader {
    template <typename T>
    static void method(T& t)
    {
        read(t);
    }
};

use<reader>(s);

Upvotes: 0

Related Questions