Construct type over enum and forward different number of arguments

437 views Asked by At

Let's say I have several types bound into a variant. On another side, I have an enum on which some of the previous types can be deduced from, so I can have a runtime pseudo factory:

#include <boost/variant.hpp>

enum class Type { W, X, Y, Z };

struct A {};

struct B
{
    B(int) {}
};

struct C
{
    C(int, int) {}
};

using variant_t = boost::variant<A, B, C>;

template<typename... Args>
variant_t MakeVariantOverEnum(Type type, Args&&... args)
{
    switch (type)
    {
        case Type::X: return B(std::forward<Args>(args)...); break;
        case Type::Z: return C(std::forward<Args>(args)...); break;            
        default:      return A(std::forward<Args>(args)...); break;
    }
}

// meant to be fully runtime
const Type GetTypeFromIO() { return Type::Z; }
const int GetFirstArgFromIO() { return 0; }
const int GetSecondArgFromIO() { return 0; }

int main()
{
    const Type type = GetTypeFromIO();
    const int firstArg = GetFirstArgFromIO();
    const int secondArg = GetSecondArgFromIO();    

    variant_t newVariant;

    if (firstArg != 0 && secondArg != 0) newVariant = MakeVariantOverEnum(type, firstArg, secondArg);
    else if (firstArg != 0)              newVariant = MakeVariantOverEnum(type, firstArg);
    else                                 newVariant = MakeVariantOverEnum(type);
}

2 things bother me in this code:

** How can I have only 1 call to MakeVariantOverEnum by passing all the arguments and then discarding those 'invalid' cases ( ==0 in my sample)? Can I do it inside the MakeVariantOverEnum with some SFINAE mechanism?

** It doesn't compile because the compiler tries to match all the constructors with all the arguments:

main.cpp: In instantiation of 'variant_t MakeVariantOverEnum(Type, Args&& ...) [with Args = {const int&, const int&}; variant_t = boost::variant<A, B, C>]':
main.cpp:44:100:   required from here
main.cpp:24:59: error: no matching function for call to 'B::B(const int&, const int&)'
         case Type::X: return B(std::forward<Args>(args)...); break;
                                                           ^
main.cpp:24:59: note: candidates are:
main.cpp:9:2: note: B::B(int)
  B(int) {}
  ^
main.cpp:9:2: note:   candidate expects 1 argument, 2 provided
main.cpp:7:8: note: constexpr B::B(const B&)
 struct B
        ^
main.cpp:7:8: note:   candidate expects 1 argument, 2 provided
main.cpp:7:8: note: constexpr B::B(B&&)
main.cpp:7:8: note:   candidate expects 1 argument, 2 provided

And so on for the other types...

So my question is: how can I make it work at this point?

Thanks!

PS: code is live here => http://coliru.stacked-crooked.com/a/4bc1e326be27b3dd

1

There are 1 answers

0
Daniel On

Easiest approach for your problem:

enum class Type { W, X, Y, Z };

struct A {};

struct B
{
    B(int) {}
};

struct C
{
    C(int, int) {}
};

using variant_t = boost::variant<A, B, C>;

variant_t MakeVariantOverEnum(Type type, int param1, int param2)
{
    switch (type)
    {
        case Type::X: return B(param1); 
        case Type::Z: return C(param1, param2);             
        default:      return A();
    }
}

// meant to be fully runtime
const Type GetTypeFromIO() { return Type::Z; }
const int GetFirstArgFromIO() { return 0; }
const int GetSecondArgFromIO() { return 0; }

int main()
{
    const Type type = GetTypeFromIO();
    const int firstArg = GetFirstArgFromIO();
    const int secondArg = GetSecondArgFromIO();    

    variant_t newVariant = MakeVariantOverEnum(type, firstArg, secondArg);
}

The variadic template just makes things more complicated and will not help you in any way.

If all your structs use the same parameters (at least: if the parameter is used, the same type is used everywhere) this will work. However if you have different parameter types for different objects, you need to consider if it wouldn't be easier to just do the switch in the main function itself (and then get the correct parameters):

Assume the following expansion:

struct D
{
    D(float) {}
}

So now you suddenly have a situation where your const int firstArg would be a float... (that will not work for B and C however)

Alternatively you specify a third parameter float param3 and only use this parameter when creating a type. (but then what about param1 and param2?)

Note: It still would be possible to use some factory pattern, but that would be quite a bit more complex (each object needs its own factory which will get the correct parameters and then create the correct struct...)