Reputation: 1406
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
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
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
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
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
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;
}
Upvotes: 2