Method chaining from namespace - c++

Sorry if this question causes any confusion, I am looking to implement this and do not know the right way to approach such a thing.
For one of my projects I want to implement method chaining. I want to incorporate the following functions:
.toVector()
.toArray()
.toBool()
...
I have thought about placing these inside a namespace, e.g:
namespace Misc {
template<typename T, typename Inverse>
vector<T> toVector(Inverse begin, Inverser end) {
// ...
// ..
}
// ...
// ...
}
This is because there could be multiple classes, these classes MIGHT be able to use these functions, so therefore, it has to be OO rather than implementing each function again and again in different classes.
Let's say I have the following class Wav which reads in the data contained in a wav file:
class Wav {
public:
Wav();
Wav(string theFileName);
void getWaveData();
protected:
vector<double> data;
};
data is explicitly stored as a vector inside of the class.
In my main I want to be able to do the following:
int main()
{
Wav wave("file.wav");
int* data = wave.getWaveData().toArray(); // Method chaining to store as an array
}
I do not know whether or not this would be possible and if so how I would approach this without implementing all of the Misc functions over and over again inside each of the classes. Is there a way to communicate between the namespace and the class without having to include all of the functions over and over again?
I hope someone has a suggestion and any questions I will try to answer.
EDIT:
I have the written the following function:
template<typename T, typename Inverse>
T* toArray(Inverse begin, Inverse end)
{
size_t size = distance(begin, end);
auto pos = 0;
T* tmp = new T[size];
for(auto i = begin; i != end; i++)
{
tmp[pos] = *i;
pos++;
}
return tmp;
}
And if I have another function:
void process()
{
}
What would I therefore need to put inside the params of process in order to accept the following:
int* data = process(toArray<int>(
std::begin(vals),
std::end(vals)
);
This is the thing that is confusing me the most?

Regarding your new function:
In order to be able to call the process method below
int* data = process(toArray<int>( vals.begin(), vals.end()) );
the parameter for the process method should match the return type of the toArray method. Perhaps you can templatize the process method too as below.
template<typename T>
T* process(T* t)
{
//more code here
return t;
}
After adding the process method as above, the call to process will compile, but you will have to make the implementation of the process method generic enough to deal with different return types from other methods like toArray.

Related

What is the C++ way to do this instead of using function pointers?

I am working on a part of a program that applies different types of filters onto an already read data part of a bitmap image. The method in question gets the data stored in a 2-dim std::vector and furthermore a pointer to the function in which the filter is applied as arguments. By that we can generically apply different filters by using this method.
My question is, are function pointers the only way to achieve this, or does C++ offer a more beautiful and more readable solution to achieve it?
This is the method this question is about. Second argument is the function pointer that is used to access the function in the if/else statement inside the for loops.
void SteganoMessage::genFilter(std::vector<std::vector<uint32_t>> *d, uint32_t (*f)(uint32_t, size_t)){
int count = 0;
int pixel = getPixel();
for(auto itOuter = d->begin(); itOuter != d->end(); ++itOuter){
for(auto itInner = itOuter->begin(); itInner != itOuter->end(); ++itInner){
if(mode == true)
*itInner = f(*itInner, sizeof(*itInner));
else
*itInner = f(*itInner, this->getImage()->getBitmapHeader()->getBitCount()/8);
displayProgress(count, pixel);
}
}
displayProgress(0);
}
Call of genFilter function:
//...
{
genFilter(data, substB);
}
//...
While substB is a function of course.
Would be very thankful for a hint that leads me into the right direction where I could research or a code snippet that shows a possible more C++ like way to do it.
Type-preserving
The usual way to pass a function (or things that can be INVOKEd) in C++ is by using a template parameter:
// version #1
template <typename F>
void func(F f)
{
static_assert(std::is_invocable_v<F, std::uint32_t, std::size_t>);
// instead of f(*itInner, sizeof(*itInner))
std::invoke(f, *itInner, sizeof(*itInner));
}
You can also use SFINAE to prevent postponing the error to instantiation time. This also enables overloading:
// version #2
template <typename F>
std::enable_if_t<std::is_invocable_v<F, std::uint32_t, std::size_t>>
func(F f)
{
// no need to static_assert here
std::invoke(f, *itInner, sizeof(*itInner));
}
Since C++20, we can use concepts:
// version #3
template <std::Invocable<std::uint32_t, std::size_t> F>
void func(F f)
{
// same as above
}
Which can be simplified further, using an abbreviated function template, to:
// version #4
void func(std::Invocable<std::uint32_t, std::size_t> auto f)
{
// same as above
}
(This is still a function template rather than an ordinary function. It is equivalent to version #3.)
Type-erasing
You can also use std::function for type erasure:
// version #5
void func(std::function<void(std::uint32_t, std::size_t)> f)
{
// ...
f(*itInner, sizeof(*itInner));
}
Compared to the type-preserving alternatives (versions #1–4), this approach may reduce code bloat, but may incur runtime overhead for virtual function calling.
I agree with the already suggested comments and answer.
But if your question was about finding a way to avoid to pass a raw pointer to function as arguments and gain more control over the given filters, I think you can create a wrapping class with a functor that will handle the applied filters.
What is the motivation behind doing this ? Because a raw pointer to function does not give you the guarantee that the function is what you expect. You can pass any function which respect the prototype but is not a real filter and can do anything.
You can solve this problem this way (explanation below the code):
enum class FILTER_TYPE {MY_FILTER, MY_OTHER_FILTER};
class Filter
{
protected:
FILTER_TYPE f_type;
public:
Filter(FILTER_TYPE ft) : f_type(ft)
{}
uint32_t operator()(uint32_t a, size_t b) const
{
switch(f_type)
{
case FILTER_TYPE::MY_FILTER: return my_filter(a, b);
case FILTER_TYPE::MY_OTHER_FILTER: return my_other_filter(a, b);
}
}
private:
uint32_t my_filter(uint32_t a, size_t b) const
{
return a+static_cast<uint32_t>(b); // completely arbitrary
}
uint32_t my_other_filter(uint32_t a, size_t b) const
{
return a*static_cast<uint32_t>(b); // completely arbitrary
}
};
As you can see, you define all your different filters in the private section. Then you redefine the operator() in order to call the proper filter (selected by the FILTER_TYPE attribute).
Then, you can write your function this way:
void SteganoMessage::genFilter(std::vector <std::vector <uint32_t>> & data, const Filter & filter)
{
int count = 0;
int pixel = getPixel();
for(auto itOuter = data.begin(); itOuter != data.end(); ++itOuter)
{
for(auto itInner = itOuter->begin(); itInner != itOuter->end(); ++itInner)
{
if(mode == true)
*itInner = filter(*itInner, sizeof(*itInner));
else
*itInner = filter(*itInner, this->getImage()->getBitmapHeader()->getBitCount()/8);
displayProgress(count, pixel);
}
}
displayProgress(0);
}
This way, you have the guarantee that the argument is a well-defined filter, and you avoid the use of raw pointer to function (that make the code more readable).
I redefined the operator() in order to use the Filter instance as a function. It makes the code more intuitive in my opinion.
Last thing, I passed the data by reference instead of the address directly.
I hope it can be a good additional information.

How to call a function from an object with a std::string

Here's my issue, I would like to call the getters/setters of one of my objects, but not directly, I want to do it by using a std::string.
I found this but it won't work on my case I think it is because my function aren't defined in my main method but in my square class. Also my function are not all defined the same way there's void(std::string) std::string() void(int)...
here's an exemple of what a would like to do.
my object square
#include <map>
#include <functional>
#include <string>
class Square{
private:
std::string name;
int width;
float happinessPoint; //extremly important for your square.
public:
void setName(std::string);
void setWidth(int);
void setHappinessPoint(float);
std::string getName()
int getWidth()
float getHappinnessPoint()
}
and my main
#include "Square.h/cpp"
int main(){
Square square = Square("Roger",2,3.5);
// here in my magicalFunction I ask to the users the new values for my square (all in std::string for now)
vector <std::string> newValueForSquare = magicalFunction();
for (unsigned int i=0; i < newValueForSquare.size(), i++){
//here I have a function which tell me if my std::string
// is in fact a float or an int
// and I would like to call each of my setters one by one to
// sets my Square to some value I asked to the user before all that.
// something like that:
// someFunction("setName","Henry")
}
}
I hope i have been clear it's pretty hard to explain something you don't know how to do. If you want me to be more specific tell me and I'll do what I can.
EDIT: What I want to do is to call for example my square.setName() with a str::string without writting this square.setName in my main.
To call functions, based on a string, you have some choices. Before I list the choices, please search the internet for "C++ factory design pattern".
If-else ladder
Lookup table
Map / Associative array
Hash table
There may be other methods, but the above come to mind.
if-else ladder (a.k.a. switch)
The problem with this method is that the switch statement doesn't work with strings nor text literals. So you'll have to suffice with if statements:
if (string == "Roger")
{
Process_Roger();
}
else if (string == "Felicity")
{
Process_Felicity();
}
else
{
Display_Error_Message();
}
Anytime you need to add a new string, you will have to add another "else if" statement to the ladder. Not only do you have to change the code, but you also have to retest it.
Lookup Table
You will need to understand function pointers for this technique and the map technique. Consider this a prerequisite.
Use a structure for mapping text strings to function pointers:
struct Text_Function_Pointer
{
const char * name;
Function_Pointer p_function;
};
static const Text_Function_Pointer table[] =
{
{"Larry", Process_Larry},
{"Felicity", Process_Felicity},
};
static const unsigned int table_size =
sizeof(table) / sizeof(table[0]);
//...
for (unsigned int i = 0; i < table_size; ++i)
{
if (search_name == table[i].name)
{
// Execute the processing function.
table[i].p_function(search_name);
break;
}
}
An issue with this technique is that all the function pointers must have the same signature. This is true for the map as well.
A nice feature is that the data in the table is constant, so it can be placed in Read-Only Memory.
Also, to add more associations, add an entry to the the table. The search / processing function hasn't changed, so it doesn't need to be tested again.
Map / Associative Array
Prerequisite: Function pointers.
Declare a std::map<std::string, Function_Pointer_Type>. Add your names and functions to the map:
std::map<std::string, Function_Pointer_Type> dispatch_table;
dispatch_table["Roger"] = Process_Roger;
dispatch_table["Felicity"] = Process_Felicity;
dispatch_table["Larry"] = Process_Larry;
//...
// Execute appropriate processing function:
(dispatch_table[search_name])();
One issue with this method is that the std::map data structure needs to be initialized; it can't be directly accessed or loaded from executable code.
Again, all functions must have the same signature.
Hash Table
The idea here is to have an array of function pointers or an array of structures with text & function pointers. Create a hash function that generates a unique array index based on the name string. Use the index to get the function pointer from the array, then execute the function via the function pointer.
Several solutions are available to you. You basically want to parse user input to fill your Square class attribute.
One way is to use the std::stoi family of functions:
std::vector<string> values { "Roger", "2", "3.5" };
std::string name = values[0]; // No problem, two strings
int width = std::stoi(values[1]); // stoi = stringToInt
float happiness = std::stof(values[2]); // stof = stringToFloat
I'm not sure why you'd need the for loop, unless there is something I didn't understand in your question. I'll update my answer accordingly.
Update 1
After reading other answers, I would like to propose my solution to your problem. As stated several times in my comments, this is not an easy answer !
I needed such a class to write a generic test engine, and this is the code I used. It works really well with any type of function (except for routines with a return type of void -- a simple template specialization would solve it though)
# include <functional>
# include <tuple>
template<int ...>
struct seq
{
};
template<int N, int ...S>
struct gens : gens<N - 1, N - 1, S...>
{
};
template<int ...S>
struct gens<0, S...>
{
typedef seq<S...> type;
};
struct callable_base
{
virtual void operator()() = 0;
virtual ~callable_base()
{ }
};
class Task
{
private:
template<class RT, class Functor, class ...Args>
struct functor : public callable_base
{
functor(RT& result, Functor func, Args ...args)
: _ret(result)
{
_func = func;
_args = std::make_tuple(args...);
}
void operator()()
{
_ret = call(typename gens<sizeof...(Args)>::type());
}
template<int ...S>
RT call(seq<S...>)
{
return (_func(std::get<S>(_args)...));
}
private:
std::function<RT(Args...)> _func;
std::tuple<Args...> _args;
RT& _ret;
};
public:
Task()
{
_functor = nullptr;
}
template<class RT, class Functor, class ...Args>
Task(RT& result, Functor func, Args... args)
{
_functor = new functor<RT, Functor, Args...>(result, func, args...);
}
void operator()()
{
(*_functor)();
}
~Task()
{
delete _functor;
}
private:
callable_base *_functor;
};
The idea behind this code is to hide the function signature in the inner class Task::functor and get the return value in the first parameter passed to the Task(...) constructor. I'm giving this code first because I think it might help some people, but also because I think it is an elegant solution to your problem. Bear in mind that to understand most of the code, you need solid C++ knowledge. I'll detail the code in subsequent updates if needed.
Here's how you'd use it:
int main()
{
int retVal;
std::string newName;
std::map<std::string, Task *> tasks {
{"setName", new Task(retVal, &Square::setName, &newName)}
...
}
/* Modify the name however you want */
...
tasks["setname"]();
}
This whole class could be optimized, of course, primarily thanks to C++14 and move semantics, universal references and all, but I kept it simple ~
A major problem is that you have to use pointers if you don't know the values of the parameters at the time you fill the task map. I'm working on another version to simplify this aspect, but I wanted to show you that C++ is not designed to do what you ask simply. Maybe you come from a functional or JS world, in which this would be trivial x)
Update 2
I just wanted to point out that with C++14, you could omit the first 3 structures that are here to help me expand my tuple in an argument list using interger_sequence

how to store values of type <T>

How to store values of datatype <T>? What I'm trying to do is to have a function that will work for long or double types. For example, to return median of 5 numbers (because now I have two classes, one for long, one for double). Two things I do not know how to:
how to store <T> values
is it possible to call function with return type <T> from function of return type <T>
This is non/working code, stripped to be readable. The code is for Arduino (or other similar microcontrolers). Thank you.
//how to store custom type?
//in practice, this may be a single value, array,
//or struct with one of the members being T
T values[5]={2,6,8,9,11};
//public
template <typename T> T getFirst (){
T result = getPos[0];
return result;
}
//private
template <typename T> T getPos (byte index){
T result = values[index];
return result;
}
void setup() {
// put your setup code here, to run once:
double k;
k=getFirst()
}
void loop() {
// put your main code here, to run repeatedly:
}
EDIT 4, THE FINAL THOUGHTS:
Since some users reported too many edits, I've decided to delete all of it. I've left only the original question. I will give my final findings in a new, separate anwser.
Do you mean something like this?
template<typename T>
class medianbuffer{
public:
// Should work like this
medianbuffer() { /**/ }
~medianbuffer() { /**/ } // "~" = destructor , no expecting to happen
T getFirst (){
T result = getPos(0);
return result;
}
private:
T getPos (byte index){
T result = values[index];
return result;
}
T values[5]={2,6,8,9,11};
};
All function using the template typename will have to be defined in the header file AFAIK.
You will then have one medianbuffer class per type that you use. All functions still need a definition.
Edit this answer is no longer relevant since the question has been changed considerably. (End edit)
A function that computes a median of 5 values needs to get 5 values as an argument. This could be a standard container or a plain pointer to a legacy array or a pair of iterators. The function doesn't need to be concerned with storage of these values. The caller stores them.
template <typename T>
T median_of_5 (const std::array<T, 5>&); // a standard no-overhead container
template <typename T>
T median_of_5 (const T*); // a legacy array
template <typename Iter>
decltype(*std::declval<Iter>())
median_of_5 (Iter first, Iter last); // two iterators
// or another way
template <typename Iter>
typename std::iterator_traits<Iter>::value_type
median_of_5 (Iter first, Iter last); // two iterators
If a function template needs temporary storage for some data of type T, just declare it in the function:
template <typename T>
T median_of_5 (const std::array<T, 5>&)
{
T values[5]; // no problem here
...
}
On the off chance you need to actually store five values outside of any function template using a generic type T, and you can use C++14, you can use a variable template:
template <typename T>
T values[5] { 2,6,8,9,11 };
But this is most probably not what you need.
So, here are some findings that I have now.
before trying everything you can find online, first create new class, and play with it. Trying to use type of <T> works better or only this way (do not know when).
do not use separate files for .h and .cpp. I'm not sure why is this a problem, since compiler is after all "merging" all of the files together anyway, but it turns out that if you do not follow this advide you will get errors that smart people can understand - but you will not, and there goes - two days are wasted.
At the moment of writing this, this is all that I know. My guess that in following years, a lot of people may stumble here, and that the ones who know better try to give some better explaned anwsers, or at least fill the gaps.
/*Before class with prototypes, indicate that Template T will be used
not sure when to use first or second line, seems that both work in this scenario*/
//template<class T>
template<typename T>
class medianbuffer{
public:
medianbuffer() { /**/ } //since no cpp will be used, do not forget to create proper functions {} in this file
~medianbuffer() { /**/ }
T getFirst();
//you may also try to define it here
//T getFirst();{
// T result = getPos(1);
// return result;
//}
private:
T getPos (byte index){
T result = values[index];
return result;
}
T values[5]={2,6,8,9,11};
};
//Here is one of your bigest time waste, and it's not an obvious one - put all in one file!
/*The following part is usually in cpp files, but you will see a lot of "undefined" or similar warnings
Because templates are compiled when required, this forces a restriction for multi-file projects:
the implementation (definition) of a template class or function must be in the same file as its declaration.
That means that we cannot separate the interface in a separate header file,
and that we must include both interface and implementation in any file that uses the templates.
http://www.cplusplus.com/doc/oldtutorial/templates/
*/
//if writing methods here, remember that every method must have "template" in front,
//do not forget to put <T>, since you will get a lot of cryptic errors
template<typename T>
T medianbuffer<T>::getFirst(){
T result = getPos(1);
return result;
}
//type of T is actually passed to its class this way
medianbuffer <double>mb_d;
medianbuffer <int> mb_i;
void setup() {
Serial.begin(57600);
debug();
}
void debug(){
//class instantiated as double, you can use it normaly
double first_d=mb_d.getFirst();
Serial.print("\n double type --> ");
Serial.print(first_d);
//class instantiated as int
int first_i=mb_i.getFirst();
Serial.print("\n int type --> ");
Serial.print(first_i);
}
void loop() {
// put your main code here, to run repeatedly:
}
Thanks n.m. and FredyKay, your help was very appreciated.

c++ template in function

I want to make a generic Array function. In my API, I have generic container that I need to cast to the right class, but I want to make it generic
template<class T>
void UT::printArray(CCArray* arr, T type)
{
CCObject *aIt = NULL;
CCARRAY_FOREACH(arr,aIt )
{
T *aElm = static_cast<T*>(aIt );
int col = aElm ->getColNum();
int row = aElm ->getRowNum();
CCLOG("col:%d row:%d",col,row);
}
}
This does not compile right, and also I need to make new T object each time I call this function. What is the right way for this?
Of course I don't know what your CCArray is but I can modify your function:
template<class T>
void UT::printArray(CCArray* arr)
{
CCObject *aIt = NULL;
CCARRAY_FOREACH(arr,aIt )
{
T *aElm = static_cast<T*>(aIt );
int col = aElm ->getColNum();
int row = aElm ->getRowNum();
CCLOG("col:%d row:%d",col,row);
}
}
I've removed your second T type argument. You'd invoke this as printArray<myType>(arr) explicitly rather than having T inferred from your (unused) argument.
As someone said in a comment your best solution would be to read about iterators and make your CCArray return a proper begin() and end() and then you could use many standard algorithms (e.g. sort) against your container.

Calling templated function with type unknown until runtime

I have a this function to read 1d arrays from an unformatted fortran file:
template <typename T>
void Read1DArray(T* arr)
{
unsigned pre, post;
file.read((char*)&pre, PREPOST_DATA);
for(unsigned n = 0; n < (pre/sizeof(T)); n++)
file.read((char*)&arr[n], sizeof(T));
file.read((char*)&post, PREPOST_DATA);
if(pre!=post)
std::cout << "Failed read fortran 1d array."<< std::endl;
}
I call this like so:
float* new_array = new float[sizeof_fortran_array];
Read1DArray(new_array);
Assume Read1DArray is part of a class, which contains an ifstream named 'file', and sizeof_fortran_array is already known. (And for those not quite so familiar with fortran unformatted writes, the 'pre' data indicates how long the array is in bytes, and the 'post' data is the same)
My issue is that I have a scenario where I may want to call this function with either a float* or a double*, but this will not be known until runtime.
Currently what I do is simply have a flag for which data type to read, and when reading the array I duplicate the code something like this, where datatype is a string set at runtime:
if(datatype=="float")
Read1DArray(my_float_ptr);
else
Read1DArray(my_double_ptr);
Can someone suggest a method of rewriting this so that I dont have to duplicate the function call with the two types? These are the only two types it would be necessary to call it with, but I have to call it a fair few times and I would rather not have this duplication all over the place.
Thanks
EDIT:
In response to the suggestion to wrap it in a call_any_of function, this wouldnt be enough because at times I do things like this:
if(datatype=="float")
{
Read1DArray(my_float_ptr);
Do_stuff(my_float_ptr);
}
else
{
Read1DArray(my_double_ptr);
Do_stuff(my_double_ptr);
}
// More stuff happening in between
if(datatype=="float")
{
Read1DArray(my_float_ptr);
Do_different_stuff(my_float_ptr);
}
else
{
Read1DArray(my_double_ptr);
Do_different_stuff(my_double_ptr);
}
If you think about the title you will realize that there is a contradiction in that the template instantiation is performed at compile time but you want to dispatch based on information available only at runtime. At runtime you cannot instantiate a template, so that is impossible.
The approach you have taken is actually the right one: instantiate both options at compile time, and decide which one to use at runtime with the available information. That being said you might want to think your design.
I imagine that not only reading but also processing will be different based on that runtime value, so you might want to bind all the processing in a (possibly template) function for each one of the types and move the if further up the call hierarchy.
Another approach to avoid having to dispatch based on type to different instantiations of the template would be to loose some of the type safety and implement a single function that takes a void* to the allocated memory and a size argument with the size of the type in the array. Note that this will be more fragile, and it does not solve the overall problem of having to act on the different arrays after the data is read, so I would not suggest following this path.
Because you don't know which code path to take until runtime, you'll need to set up some kind of dynamic dispatch. Your current solution does this using an if-else which must be copied and pasted everywhere it is used.
An improvement would be to generate a function that performs the dispatch. One way to achieve this is by wrapping each code path in a member function template, and using an array of member function pointers that point to specialisations of that member function template. [Note: This is functionally equivalent to dynamic dispatch using virtual functions.]
class MyClass
{
public:
template <typename T>
T* AllocateAndRead1DArray(int sizeof_fortran_array)
{
T* ptr = new T[sizeof_fortran_array];
Read1DArray(ptr);
return ptr;
}
template <typename T>
void Read1DArrayAndDoStuff(int sizeof_fortran_array)
{
Do_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
}
template <typename T>
void Read1DArrayAndDoOtherStuff(int sizeof_fortran_array)
{
Do_different_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
}
// map a datatype to a member function that takes an integer parameter
typedef std::pair<std::string, void(MyClass::*)(int)> Action;
static const int DATATYPE_COUNT = 2;
// find the action to perform for the given datatype
void Dispatch(const Action* actions, const std::string& datatype, int size)
{
for(const Action* i = actions; i != actions + DATATYPE_COUNT; ++i)
{
if((*i).first == datatype)
{
// perform the action for the given size
return (this->*(*i).second)(size);
}
}
}
};
// map each datatype to an instantiation of Read1DArrayAndDoStuff
MyClass::Action ReadArrayAndDoStuffMap[MyClass::DATATYPE_COUNT] = {
MyClass::Action("float", &MyClass::Read1DArrayAndDoStuff<float>),
MyClass::Action("double", &MyClass::Read1DArrayAndDoStuff<double>),
};
// map each datatype to an instantiation of Read1DArrayAndDoOtherStuff
MyClass::Action ReadArrayAndDoOtherStuffMap[MyClass::DATATYPE_COUNT] = {
MyClass::Action("float", &MyClass::Read1DArrayAndDoOtherStuff<float>),
MyClass::Action("double", &MyClass::Read1DArrayAndDoOtherStuff<double>),
};
int main()
{
MyClass object;
// call MyClass::Read1DArrayAndDoStuff<float>(33)
object.Dispatch(ReadArrayAndDoStuffMap, "float", 33);
// call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
object.Dispatch(ReadArrayAndDoOtherStuffMap, "double", 542);
}
If performance is important, and the possible set of types is known at compile time, there are a few further optimisations that could be performed:
Change the string to an enumeration that represents all the possible data types and index the array of actions by that enumeration.
Give the Dispatch function template parameters that allow it to generate a switch statement to call the appropriate function.
For example, this can be inlined by the compiler to produce code that is (generally) more optimal than both the above example and the original if-else version in your question.
class MyClass
{
public:
enum DataType
{
DATATYPE_FLOAT,
DATATYPE_DOUBLE,
DATATYPE_COUNT
};
static MyClass::DataType getDataType(const std::string& datatype)
{
if(datatype == "float")
{
return MyClass::DATATYPE_FLOAT;
}
return MyClass::DATATYPE_DOUBLE;
}
// find the action to perform for the given datatype
template<typename Actions>
void Dispatch(const std::string& datatype, int size)
{
switch(getDataType(datatype))
{
case DATATYPE_FLOAT: return Actions::FloatAction::apply(*this, size);
case DATATYPE_DOUBLE: return Actions::DoubleAction::apply(*this, size);
}
}
};
template<void(MyClass::*member)(int)>
struct Action
{
static void apply(MyClass& object, int size)
{
(object.*member)(size);
}
};
struct ReadArrayAndDoStuff
{
typedef Action<&MyClass::Read1DArrayAndDoStuff<float>> FloatAction;
typedef Action<&MyClass::Read1DArrayAndDoStuff<double>> DoubleAction;
};
struct ReadArrayAndDoOtherStuff
{
typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<float>> FloatAction;
typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<double>> DoubleAction;
};
int main()
{
MyClass object;
// call MyClass::Read1DArrayAndDoStuff<float>(33)
object.Dispatch<ReadArrayAndDoStuff>("float", 33);
// call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
object.Dispatch<ReadArrayAndDoOtherStuff>("double", 542);
}