1

I have the following template struct:

 template<class T> struct S {
    T a,b,c,d,e,f,g,h;
 };

Then I try to do this:

S<double> sDouble;
S<float> sFloat;

sDouble = sFloat; //Compile error here, since sDouble and sFloat have «unrelated» types

What should I do? I have lots of such structures with lots of data members in them. I tired of providing custom templated operator= for each of the struct just to find later that I forgot to assign some member.

P.S. The situation above is very inconsistent with the code below which can be compiled:

template<class T> using V = T; 

int main()
{
  V<double> vDouble;
  V<float> vFloat;

  vDouble = vFloat; //Can be compiled

  return 0;
}
Anton Sukhinov
  • 158
  • 1
  • 8
  • 1
    No way around it. You should read each `S` as a separate type. The assignment operator for two different types must be written. I suppose the best you can do it is set up a macro to handle the auto creation of the code. – cplusplusrat Oct 17 '19 at 07:31
  • Your other example is kind of an apples-to-oranges comparison. In that one, it's a plain conversion between `double` and `float`, regardless of whether you spell that with a templated alias or not. In your main question, you need extra rules about when the compiler would generate such an assignment operator and any special cases that it would need to deal with. – chris Oct 17 '19 at 07:57

3 Answers3

3

The compiler is unable to generate implicitly an assignment operator for two different types. You need to define it explicitly. For example

template<class T> struct S 
{
    T a,b,c,d,e,f,g,h;

    template <class U>
    S<T> & operator =( const S<U> &s )
    {
        a = T( s.a );
        b = T( s.b );
        c = T( s.c );
        d = T( s.d );
        e = T( s.e );
        f = T( s.f );
        g = T( s.g );
        h = T( s.h );

        return *this;
    }
};
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Please don't forget to at least `static_cast` before assignment + checking that types are trivial/correctly copyable. – The Quantum Physicist Oct 17 '19 at 07:36
  • @TheQuantumPhysicist why should I have `static_cast`? I think it will be good to have a warning if I assign `double` to `int`, for example. And `static_cast` will silent this warning. – Anton Sukhinov Oct 17 '19 at 07:44
  • @AntonSukhinov I used casting to allow using the assignment operator when the conversion constructor is explicit. – Vlad from Moscow Oct 17 '19 at 07:47
  • @AntonSukhinov If you're OK with having a warning, then why are you allowing it in the first place? You either want the conversion to happen, so you `static_cast`, or you don't want it to happen, so you fire a `static_assert` and prevent the compilation from continuing. Keeping warnings floating in your program is not a good idea. – The Quantum Physicist Oct 17 '19 at 07:47
3

First, I would suggest taking another look at whether it makes more sense to group these members. For example, maybe it's possible that an array makes sense:

std::array<T, 8> data;

In this case, the problem is reduced as far as you can group the members.

If that isn't a good path, I'm afraid that until static reflection is in the language, the best you can do is something like [Boost.]PFR (formerly known as magic_get), which tries to emulate static reflection through some clever and some hacky (cough copy-pasted structured binding attempts) machinery. With C++17:

template<typename OtherT>
S<T>& operator=(const S<OtherT>& other) {
    auto this_tuple = boost::pfr::structure_tie(*this);
    auto other_tuple = boost::pfr::structure_tie(other);
    this_tuple = other_tuple;

    return *this;
}

Note that in particular, the class must be usable with structured bindings.

Barring these, your best option is probably either writing it all out or making use of some more powerful macro-based techniques that allow you to declare or adapt a class in such a way that it becomes reflectable. For example, Boost.Hana and Boost.Fusion both offer such facilities. The tradeoff is that the code will be considerably less straightforward, especially if all you need is this one feature.

chris
  • 60,560
  • 13
  • 143
  • 205
1

As the other answers suggest, it is possible to achieve the behavior you want with (a LOT) of boost metaprogramming. Using answers to these questions: C++ preprocessor: avoid code repetition of member variable list, Iterate through Struct and Class Members I cobbled something together which iterates through all reflectable members of two containers and assigns them after casting.

However I don't know how robust this approach is and probably would recommend against using it in production code.

#include <boost/preprocessor.hpp>
#include <boost/type_traits.hpp>
#include <boost/mpl/range_c.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/bind.hpp>

#define REM(...) __VA_ARGS__
#define EAT(...)

#define TYPEOF(x) DETAIL_TYPEOF(DETAIL_TYPEOF_PROBE x,)
#define DETAIL_TYPEOF(...) DETAIL_TYPEOF_HEAD(__VA_ARGS__)
#define DETAIL_TYPEOF_HEAD(x, ...) REM x
#define DETAIL_TYPEOF_PROBE(...) (__VA_ARGS__),
#define STRIP(x) EAT x
#define PAIR(x) REM x

template<class M, class T>
struct make_const
{
    typedef T type;
};

template<class M, class T>
struct make_const<const M, T>
{
    typedef typename boost::add_const<T>::type type;
};

#define REFLECTABLE(...) \
static const int fields_n = BOOST_PP_VARIADIC_SIZE(__VA_ARGS__); \
friend struct reflector; \
template<int N, class Self> \
struct field_data {}; \
BOOST_PP_SEQ_FOR_EACH_I(REFLECT_EACH, data, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))

#define REFLECT_EACH(r, data, i, x) \
PAIR(x); \
template<class Self> \
struct field_data<i, Self> \
{ \
    Self & self; \
    field_data(Self & self) : self(self) {} \
    \
    typename make_const<Self, TYPEOF(x)>::type & get() \
    { \
        return self.STRIP(x); \
    }\
    typename boost::add_const<TYPEOF(x)>::type & get() const \
    { \
        return self.STRIP(x); \
    }\
    const char * name() const \
    {\
        return BOOST_PP_STRINGIZE(STRIP(x)); \
    } \
}; \

struct reflector
{
    //Get field_data at index N
    template<int N, class T>
    static typename T::template field_data<N, T> get_field_data(T& x)
    {
        return typename T::template field_data<N, T>(x);
    }

    // Get the number of fields
    template<class T>
    struct fields
    {
        static const int n = T::fields_n;
    };
};

// Custom code for struct assignment below here
struct dual_field_visitor
{
    template<class CA, class CB, class Visitor, class T>
    void operator()(CA & ca, CB & cb, Visitor v, T)
    {
        v(reflector::get_field_data<T::value>(ca), reflector::get_field_data<T::value>(cb));
    }
};

template<class CA, class CB, class Visitor>
void dual_visit_each(CA & ca, CB & cb, Visitor v)
{
    typedef boost::mpl::range_c<int,0,reflector::fields<CA>::n> range;
    boost::mpl::for_each<range>(boost::bind<void>(dual_field_visitor(), boost::ref(ca), boost::ref(cb), v, _1));
}

struct assign_visitor
{
    template<class FieldDataA, class FieldDataB>
    void operator()(FieldDataA fa, FieldDataB fb)
    {
        fa.get() = static_cast<std::decay_t<decltype(fa.get())>>(fb.get());
    }
};

template<typename T>
struct Container
{
    REFLECTABLE
    (
        (T) a,
        (T) b
    )

    template<typename U>
    Container & operator=(const Container<U> & c)
    {
        dual_visit_each(*this, c, assign_visitor());
        return *this;
    }

    Container & operator=(const Container & c)
    {
        if(this != & c)
        {
            dual_visit_each(*this, c, assign_visitor());
        }
        return *this;
    }
};

int main()
{
    Container<int> a {1, 2};
    Container<float> b {3.0f, 4.0f};
    Container<float> c {5.0f, 6.0f};

    a = b;
    b = c;

    return 0;
}
Devon Cornwall
  • 957
  • 1
  • 7
  • 17