2

I have that code :

template<typename Type, typename... Extentions>
class Variable : virtual public GenericVar<Type>, public Extentions...
{
        static_assert((std::is_base_of_v<GenericVar<Type>, Extentions> && ...), "Only virtual base of GenericVar<T> allowed as variadic.");

    public:
        template<typename... Args>
        Variable(Type initValue, Args... args);
        virtual ~Variable();
};

template<typename Type, typename... Extentions>
template<typename... Args>
Variable<Type,Extentions...>::Variable(Type initValue, Args... args) : GenericVar<Type>(), Extentions(args)...
{
    this->value = initValue;
}

template<typename Type, typename... Extentions>
Variable<Type,Extentions...>::~Variable()
{

}

usage :


template<typename T>
using Constant = Variable<T, Immutable<T>, Forceable<T>>;

int main(int argc, char *argv[])
{

    Variable<float, Forceable<float>> test2(1.2f,nullptr);
    Constant<float> constant(4.578f, nullptr, nullptr);
    Variable<quint32, Forceable<quint32>, ChangeCheckable<quint32>> value(45, nullptr, nullptr);
}

I'd like to create a generic, extensible class to build all my var types.
and it works great! but...

I haven't found a way to avoid nullptr in the constructor. for example "Forcable" :

template<typename T>
class Forceable : virtual public GenericVar<T>
{
    public:
        Forceable(std::nullptr_t);
        ...
        ...
};

template<typename T>
Forceable<T>::Forceable(std::nullptr_t)
{
    this->forced = false;
}

If I remove it, I get this error:

xxxxxxxxxxxxxxxx\main.cpp:16: erreur : mismatched argument pack lengths while expanding 'Extentions'
In file included from xxxxxxxxxxxxxxxx/Variables/Extention/Detectable.h:6,
                 from xxxxxxxxxxxxxxxx\main.cpp:5:
xxxxxxxxxxxxxxxx/Variables/Variable.h: In instantiation of 'Variable<Type, Extentions>::Variable(Type, Args ...) [with Args = {}; Type = float; Extentions = {Forceable<float>}]':
xxxxxxxxxxxxxxxx\main.cpp:16:49:   required from here
xxxxxxxxxxxxxxxx/Variables/Variable.h:22:108: error: mismatched argument pack lengths while expanding 'Extentions'
   22 | Variable<Type,Extentions...>::Variable(Type initValue, Args... args) : GenericVar<Type>(), Extentions(args)...
      |                                                                                                            ^~~

Because of : "Extentions(args)..." instead of "Extentions()" in this case, I guess I think there's a solution... but I haven't found it!

I would like :


template<typename T>
using Constant = Variable<T, Immutable<T>, Forceable<T>>;

int main(int argc, char *argv[])
{

    Variable<float, Forceable<float>> test2(1.2f);
    Constant<float> constant(4.578f);
    Variable<quint32, Forceable<int>, ChangeCheckable<int>> value(45);
}

Deeper example :

Variable<int, Forceable<int>, Boundable<int,int>, ChangeCheckable<int>, Validable<int>, RiseDefault<int, bool, Riseable> > 
            value3(0, nullptr, {-30,50}, nullptr, nullptr, 40, new Variable<bool, Riseable>(false,nullptr));
Slane
  • 21
  • 2
  • If none of the `Extentions` take constructor arguments, you can simply remove the `Args... args` parameters from the `Variable` constructor. – chrysante Jul 30 '23 at 13:19
  • But some of them use constructor arguments :/ Yes, i haven't set an exemple with it... – Slane Jul 30 '23 at 13:26
  • _I'd like to create a generic, extensible class to build all my var types_ Quite honestly, I wouldn't do this. It looks complex, error prone, and makes the source code opaque to anyone who doesn't understand your scheme. – Paul Sanders Jul 30 '23 at 13:39
  • When all the "using" is done, it's not too complicated to use. It even seems really easy to use (if there's no nullptr...). – Slane Jul 30 '23 at 14:19
  • OK, let me put it another way: what benefits does it bring? – Paul Sanders Jul 30 '23 at 16:43

1 Answers1

2

You could make a constructor for Variable that takes the first N Extension's as parameters and default constructs the remaining ones.

Here is a not necessarily pretty implementation

#include <tuple>

template <typename... T>
struct Variable: T... {
    template <typename... U, size_t NumMissing = sizeof...(T) - sizeof...(U)>
    Variable(U&&... u): Variable(std::make_index_sequence<NumMissing>{}, std::forward<U>(u)...) {}
    
private:
    struct PrivateTag {};
    
    template <typename... U, size_t... I>
    Variable(std::index_sequence<I...>, U&&... u):
        Variable(PrivateTag{},
                 std::forward<U>(u)...,
                 std::tuple_element_t<sizeof...(U) + I, std::tuple<T...>>()...) {}
    
    template <typename... U>
    Variable(PrivateTag, U&&... u): T(std::forward<U>(u))... {
        static_assert(sizeof...(U) == sizeof...(T));
    }
};

And testing code

struct Ext {
    Ext(int = 0) {}
};

struct Ext2 {
    explicit Ext2(float = 0.0f) {}
};

// Extension that takes multiple constructor arguments
struct Ext3 {
    explicit Ext3(int, int) {}
};

int main() {
    // Default constructs both extensions
    Variable<Ext, Ext2> x;

    // Constructs Ext with value 1 and default constructs Ext2
    Variable<Ext, Ext2> y(1);

    // Constructs Ext with value 1 and Ext2 with value 1.f
    Variable<Ext, Ext2> z(1, 1.f);

    // Explicitly construct Ext3 since it takes multiple constructor aguments
    Variable<Ext, Ext3> z(0, Ext3(1, 2));
}

This behaviour is in line with how default function arguments work.

chrysante
  • 2,328
  • 4
  • 24
  • I have add an other example of extention, for more context of my problem – Slane Jul 30 '23 at 14:44
  • @Slane I don't think I understand how my solution does not solve that example. You have to replace the `nullptr` arguments by calls to the default constructors for `Boundable`, `ChangeCheckable` and `Validable`. You can't just omit them, otherwise how should the constructor of `Variable` know which argument belongs to which `Extension`? – chrysante Jul 30 '23 at 16:26