Piyush Soni
Piyush Soni

Reputation: 1406

C++ Conditional Templates Compilation based on data type

I was really hoping I could achieve this using C++ (11) Templates, but I'm facing some problem here. So we have a custom pointer object which internally could be of any of these types: a list of objects of its own type/an int/a char/bool/long/double/char* or any other primitive type, and it's decided by a flag that this object stores. There are global methods to get values of specific types from this object.

Now, my purpose is simple. I know that for my case, my object is a list of such objects, so I wanted to write a function like this as it's a common scenario:

template <typename T>
std::vector<T> List_To_Vector(Object& list)
{
    std::vector<T> vec;
    int listSize = Func_Size(list);    
    for (int i = 0; i < listSize; ++i)
    {
        //let's say this function gives list items one by one
        Object arg = Func_Next(list);
        if (std::is_same<T, int>::value || std::is_same<T, unsigned int>::value)
            vec.push_back((T)Func_IntValue(arg));
        else if (std::is_same<T, float>::value || std::is_same<T, double>::value)
            vec.push_back((T)Func_DoubleValue(arg));
        else if (std::is_same<T, std::string>::value || std::is_same<T, char*>::value)
            vec.push_back((T)Func_StringValue(arg)); //Func_StringValue returns char*, so conversion to std::string should work
        else if (std::is_same<T, bool>::value)
            vec.push_back(Func_BoolValue(arg));
        else if (std::is_same<T, char>::value)
            vec.push_back(Func_CharValue(arg));

        vec.push_back(val);
    }

    return vec;
}

int main()
{
    Object listContainingStrings = GetListOfNames();
    Object listContainingNumbers = GetListOfNumbers();
    std::vector<string> vec1 = List_To_STD_Vector<string>(listContainingStrings);
    std::vector<int> vec2 = List_To_STD_Vector<int>(listContainingNumbers);
    return 0;
}

The problem is, C++ complains here as it tries to compile the code taking T = std::string, and int to string or float to string conversions would fail. What I really wanted here was a way to compile the int part of the code when the type is detected as int, and not of any other type. I can use Template function specialization or overloading, but then I think it really defeats the purpose of templates here and I could just write 8 different functions for 8 different types (e.g. List_To_String_Vector, List_To_Int_Vector and so on).

I also tried another hack, using reinterpret_cast<T*> on the address of the each return types, and then dereferencing it to add to the vector. That kind of worked, but has Compiler warnings and I think that's undefined behavior.

Is there a way to make this work properly?

Thanks!

Upvotes: 2

Views: 2438

Answers (5)

max66
max66

Reputation: 66200

Can I play too?

I propose a solution based on a template deleted convertion function

template <typename T>
T getTValue (T const &, Object const &) = delete;

and some not-template conversion functions, with the same signature, for calling the right Func_X_Value() function; something like

int getTValue (int const &, Object const &  obj)
 { return Func_IntValue(arg); }

unsigned int getTValue (unsigned int const &, Object const &  obj)
 { return Func_IntValue(arg); }

float getTValue (float const &, Object const &  obj)
 { return Func_DoubleValue(arg); }

double getTValue (double const &, Object const &  obj)
 { return Func_DoubleValue(arg); }

char * getTValue (char const * &, Object const &  obj)
 { return Func_StringValue(arg); }

std::string getTValue (std::string const &, Object const &  obj)
 { return Func_StringValue(arg); }

char getTValue (char const &, Object const &  obj)
 { return Func_CharValue(arg); }

bool getTValue (bool const &, Object const &  obj)
 { return Func_BoolValue(arg); }

The first argument is unused and is introduced only to select the right not-template functions, so the for cycle become

for (int i = 0; i < listSize; ++i)
   vec.push_back(getTValue(T(), arg));

The template deleted function is introduced to avoid unwanted type conversions (by example: from short int to int) and impose an error, in compilation phase, if someone try to call List_To_Vector() with a wrong T.

So, by example, call

std::vector<int> vi = List_To_Vector<int>(listContainingNumbers);

should be OK but call

std::vector<long> vl = List_To_Vector<long>(listContainingNumbers);

because getTValue<long>() is deleted and there isn't a getTValue(long const &, Object const &) not-template function.

p.s.: cautions: code not tested.

Upvotes: 1

Biggy Smith
Biggy Smith

Reputation: 930

I would suggest using function specialisations to wrap each call, that way you can have precise control on whats happens for each type e.g.

template<typename T> T object_to(const Object& arg) { }

template<> int          object_to(const Object& arg) { return Func_IntValue(arg); }
template<> unsigned int object_to(const Object& arg) { return Func_IntValue(arg); }
template<> std::string  object_to(const Object& arg) { return Func_StringValue(arg); }
template<> float        object_to(const Object& arg) { return Func_DoubleValue(arg); }
template<> double       object_to(const Object& arg) { return Func_DoubleValue(arg); }
template<> bool         object_to(const Object& arg) { return Func_BoolValue(arg); }
template<> char         object_to(const Object& arg) { return Func_CharValue(arg); }

then give your Object class some standard algorithm methods and plug it into the following:

template<typename T>
std::vector<T> to_vector(const object_list& obj_list) {
    std::vector<T> vec(obj_list.size());
    std::transform(obj_list.begin(),obj_list.end(),vec.begin(),[](const Object& obj) {
        return object_to<T>(obj);
    });
    return vec;
}

Upvotes: 0

eerorika
eerorika

Reputation: 238351

I suggest using a helper to deduce correct overload for conversion:

class convert
{
    const Object& from;
public:
    explicit convert(const Object& from): from(from) {}

    operator char()        const { return Func_CharValue(from); }
    operator bool()        const { return Func_BoolValue(from); }
    operator std::string() const { return Func_StringValue(from); }
    operator const char*() const { return Func_StringValue(from); }
    // ...
};

// ...

vec.push_back(convert(arg));

No need for templates.

This does have a drawback of having to repeat each concrete type even if they use the same conversion function. But you don't have terribly many of those. The overloads could be augmented with a template conversion operator that is disabled by default, but enabled for the types that reuse a common conversion function.

Upvotes: 0

ildjarn
ildjarn

Reputation: 62975

The fundamental theorem of software engineering:

We can solve any problem by introducing an extra level of indirection.

List_To_Vector is doing too much – both conversion from Object to T, and filling a vector; abstract out the former and the solution becomes natural. First, List_To_Vector:

template<typename T>
std::vector<T> List_To_Vector(Object& list) {
    std::vector<T> vec;
    for (int i = 0, listSize = Func_Size(list); i < listSize; ++i) {
        vec.push_back(Object_To<T>(Func_Next(list)));
    }
    return vec;
}

Now you can just overload or specialize Object_To as necessary. Here's one way, using SFINAE:

// not strictly necessary, but reduces noise a bit
template<bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

template<typename T>
auto Object_To(Object arg)
 -> enable_if_t<std::is_same<T, int>{} || std::is_same<T, unsigned>{}, T>
{
    return (T)Func_IntValue(arg);
}

template<typename T>
auto Object_To(Object arg)
 -> enable_if_t<std::is_same<T, float>{} || std::is_same<T, double>{}, T>
{
    return (T)Func_DoubleValue(arg);
}

template<typename T>
auto Object_To(Object arg)
 -> enable_if_t<std::is_same<T, std::string>{} || std::is_same<T, char*>{}, T>
{
    return (T)Func_StringValue(arg);
}

template<typename T>
auto Object_To(Object arg) -> enable_if_t<std::is_same<T, bool>{}, T>
{
    return Func_BoolValue(arg);
}

template<typename T>
auto Object_To(Object arg) -> enable_if_t<std::is_same<T, char>{}, T>
{
    return Func_CharValue(arg);
}

Using something like boost::fusion::map<> can make this much cleaner if you can afford the dependency.

Upvotes: 4

Quentin
Quentin

Reputation: 63124

Let's act one level below your List_To_Vector function. The issue I see is that you have a collection of unrelated Func_*Value functions, so let's gather them under a single, type-aware template once and for all:

template <class>
struct valueGetter;

template <> struct valueGetter<float>  { static constexpr auto &get = Func_DoubleValue; };
template <> struct valueGetter<double> { static constexpr auto &get = Func_DoubleValue; };
template <> struct valueGetter<int>    { static constexpr auto &get = Func_IntValue;    };
// etc.

Now List_To_Vector becomes trivial:

template <typename T>
std::vector<T> List_To_Vector(Object& list)
{
    std::vector<T> vec;
    int listSize = Func_Size(list);    
    for (int i = 0; i < listSize; ++i)
        vec.push_back(valueGetter<T>::get(Func_Next(list)));

    return vec;
}

See it live on Coliru

Upvotes: 2

Related Questions