Help compacting Functions

Pages: 12
Hey guys,

I need a way to compact functions.
In order to understand my question, please go to

www.github.com/juliarLang/juliar

Click DestkopServer/src/main.cpp

At the bottom, you will see something like if(command == "block") return block(param); if(command == "section") return section(param);

My question is, is there a way in C++11 (without boost libraries preferably) to have it so that you can execute function "command_varaible"(mparam). This is compile time replacement(so no need to worry about run time). I've heard of using templates and MACROS, but what is the best way to achieve this without compromising run-time performance.

2) In that same file you may notice I am using if(args == 0) then execute function with just parameter, if(args==1) then execute same function with just parameter and 1 argument, if (args==2) then execute same function with just parameter and 2 argument. Is there a way to create a template or MACRO for that. I know I can just pass an array pointer, but what do you guys recommend.

Any help would be appreciated.

(Please do not bash me or the language or coding style that I use as the program still in early stages and I am still figuring out problems. )
My question is, is there a way in C++11 (without boost libraries preferably) to have it so that you can execute function "command_varaible"(mparam).
Nope. Your code in particular even branches to different overloads based on the contents of an std::vector<std::string>.

This is compile time replacement(so no need to worry about run time).
It's not. You're branching based on the value of a run time object. Neither templates nor macros can help here.

You could do this:
1
2
3
4
5
6
7
8
9
std::map<std::string, std::string (*)(std::vector<std::string> &)> function_map;
// Populate function_map at some point.

// In picker():
auto it = function_map.find(command);
if (it == function_map.end())
    //command doesn't exist.

return (*it)(args);
Last edited on
Helios,

I was thinking of doing something similar, but wouldn't the code length and execution time be the same?

What are benefits of using map benefits vs if condition?
Would this suggestion be faster because map is ordered so there is a faster lookup instead of going to each if condition? But you will still need to take the map and go through the linked list. So run time may be equivalent?
Last edited on
Would this suggestion be faster because map is ordered so there is a faster lookup instead of going to each if condition?
Yes, it'd be a little faster.

But you will still need to take the map and go through the linked list.
std::map is typically implemented as a red-black tree, not as a list, and has guaranteed O(log n) lookup time, while your code takes O(n) time.
If you prefer, you can use std::unordered_map, which takes O(1) on average (O(n) in the worst case).
A perfect hash map looks up in O(1) in the worst case, but you can no longer know when a key does not appear in the map.

IMO, regardless of whether a map performs better or worse, the benefit of a data-based approach is that you avoid repetitive code.
Thanks! I will use that.You mentioned O(1) performance time. Is this achievable in C++?
It's possible in any programming language.
By that I mean O(1) in the worst case time. What do I need to do to achieve perfect hash map look up in C++? i.e. is there any built in structs to achieve that (besides having one element)?

Also, std::map<std::string, std::string (*)(std::vector<std::string> &)> function_map;
Could I do:

std::string read{
return "read";
}
std::map<std::string, std::string (*)(std::vector<std::string> &)> function_map = {{"read", read(args)}}

(*function_map["read"])();

Or what would be correct syntax in calling and assigning the function. Sorry I am C++ NOOB :|
Last edited on
What do I need to do to achieve perfect hash map look up in C++? i.e. is there any built in structs to achieve that (besides having one element)?
C++ doesn't provide any containers with perfect hash functions, but they're not difficult to implement. This article provides a simple explanation: http://stevehanov.ca/blog/index.php?id=119

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Be careful with very simple names for your functions.
// Resolving ambiguous overloads when getting pointers to functions is cumbersome.
std::string some_function(std::vector<std::string> &){
    //...
}

void populate_function_map(){
    function_map["some_function"] = some_function;
}

//...

// This is legal, but avoid doing it. If function_map doesn't contain the key,
// operator[]() will return a null pointer, which you will then call. I don't think
// I need to say what happens when a null function pointer is called.
(*function_map["some_function"])(args);
Dear Helios,

I will definitely be using

std::map<std::string, std::string (*)(std::vector<std::string> &)> function_map;
// Populate function_map at some point.

// In picker():
auto it = function_map.find(command);
if (it == function_map.end())
//command doesn't exist.

return (*it)(args);

But what if I have an ambiguous amount of parameters, say I have functions that can get 0-5 arguments. Would I need to define a new map for it?
args is being passed to the function. The function should decide what to do based on args.size().
Is there a way to have something like:


string globalfunctioncaller(*funcname, args){
if(args.empty()) return *funcname();
else if(args.size() == 1) return *funcname(args[0]);
else if(args.size() == 2) return *funcname(args[0],args[1]);
else if(args.size() == 3) return *funcname(args[0],args[1],args[2]);
else{
return *funcname(args[0],args[1],args[2],args[3])
}
}
No. Unfortunately you've chosen a feature for your language that translates to bucketloads of C++ boilerplate code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
#include <utility>

// globalfunctioncaller
template < typename FN, typename... ARGS > auto dispatch( FN&& fn, ARGS&&... args )
{ return std::forward<FN>(fn)( std::forward<ARGS>(args)... ) ; }

int foo() { std::cout << "int foo()\n" ; return 5 ; }

std::string bar( double d )
{ std::cout << "std::string bar(double)\n" ; return "bar result: " + std::to_string(d) ; }

std::string baz( double d, int v )
{ std::cout << "std::string baz(double,int)\n" ; return "baz => " + std::to_string(d+v) ; }

double foobar( double d, int v, short s )
{ std::cout << "double foobar(double,int,short)\n" ; return d+v+s ; }

int main()
{
    const auto a = dispatch(foo) ; std::cout << a << "\n\n" ;
    const auto b = dispatch( bar, 23 ) ; std::cout << b << "\n\n" ;
    const auto c = dispatch( baz, 23.6, 7 ) ; std::cout << c << "\n\n" ;
    const auto d = dispatch( foobar, 23.6, 7, 8 ) ; std::cout << d << "\n\n" ;
}

http://coliru.stacked-crooked.com/a/ae6bbe6c1a6ddf4e
Last edited on
Yeah, but his parameters come from a vector, and his function comes from a string.
I don't think the template will work for me because I have ambiguous amount of parameters. I was thinking of a way to unpack a vector if possible or at least generate functions if you have 4 parameters you will generate 4 + (1 empty) functions etc...

If I just passed a vector args to each function and let function deal with size. Would that simplify the boilerplate? or would that just create more problems?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
std::string some_function(std::vector<std::string> &args){
    if (args.size() == 0){
        //...
    }else if (args.size == 1){
        //...
    }else if (args.size == 2){
        //...
    }
    //etc.
}

std::string some_other_function(std::vector<std::string> &args){
    if (args.size() == 0){
        //...
    }else if (args.size == 1){
        //...
    }else if (args.size == 2){
        //...
    }
    //etc.
}
Would something like this work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string globalfunctioncaller(*funcname, args){
      if(args.empty()) return *funcname();
      else if(args.size() == 1) return *funcname(args[0]);
      else if(args.size() == 2) return *funcname(args[0],args[1]);
      else if(args.size() == 3) return *funcname(args[0],args[1],args[2]);
      else return *funcname(args[0],args[1],args[2],args[3])
}
std::string hello(std::string name, std::string option){
        return "Hello "+name+" "+option;
}
std::map<std::string, std::string (*)(std::vector<std::string> &)> function_map =
{
   {"hello", hello}
};
// In picker():
auto it = function_map.find(command);
if (it == function_map.end())
    //command doesn't exist.

return globalfunctioncaller(*it,args);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <iomanip>

#ifdef __cpp_lib_experimental_any

    #include <experimental/any>
    namespace stx = std::experimental ;

#else

    #include <boost/any.hpp>
    namespace stx = boost ;

#endif // __cpp_lib_experimental_any

using map_type = std::unordered_map< std::string, std::function< stx::any( const std::vector<stx::any>& ) > > ;

stx::any call_it( const std::string& fn_name, const std::vector<stx::any>& args, const map_type& map ) // may throw
{
    const auto iter = map.find(fn_name) ;
    if( iter != map.end() ) return iter->second(args) ;
    else return {} ;
}

std::string foo( int a, double b, short c )
{ std::cout << "std::string foo(int,double,short) => " ; return std::to_string(a+b+c) ; }

stx::any foo_wrapper( const std::vector<stx::any>& args ) // may throw
{
    if( args.size() != 3 ) throw std::invalid_argument( "invalid number of args" ) ;
    return foo( stx::any_cast<int>(args[0]), stx::any_cast<double>(args[1]), stx::any_cast<short>(args[2]) ) ;
}

struct A
{
    std::size_t bar( int a, const std::string& b ) const
    { std::cout << "int A::bar(int,const std::string&) => " ; return a + b.size() + value ; }

    int value = 234 ;
};

stx::any bar_wrapper( const std::vector<stx::any>& args ) // may throw
{
    if( args.size() != 3 ) throw std::invalid_argument( "invalid number of args" ) ;
    return stx::any_cast<A*>(args[0])->bar( stx::any_cast<int>(args[1]), stx::any_cast<std::string>(args[2]) );
}

int main()
{
    map_type fn_map = { { "foo", foo_wrapper }, { "bar", bar_wrapper } } ;

    auto result = call_it( "foo", { stx::any(12), stx::any(34.6), stx::any( short(7) ) }, fn_map ) ;
    std::cout << "result: " << std::quoted( stx::any_cast<std::string>(result) ) << '\n' ;

    A a ;
    using namespace std::literals ;
    result = call_it( "bar", { stx::any( std::addressof(a) ), stx::any(99), stx::any( "abcdefgh"s ) }, fn_map ) ;
    std::cout << "result: " << stx::any_cast<std::size_t>(result) << '\n' ;
}

http://coliru.stacked-crooked.com/a/eb7d20046b489246
Randy2017: No, because a function pointer can only point to a single function, and your globalfunctioncaller() is passing different numbers of parameters, which would involve different functions.
You could do this:
1
2
3
4
5
6
7
8
9
typedef std::string (*fp)(const std::string &, const std::string &, const std::string &, const std::string &);

string globalfunctioncaller(fp f, const std::vector<std::string> &args){
      if(args.empty()) return f("", "", "", "");
      else if(args.size() == 1) return f(args[0], "", "", "");
      else if(args.size() == 2) return f(args[0], args[1], "", "");
      else if(args.size() == 3) return f(args[0], args[1], args[2], "");
      else return f(args[0], args[1], args[2], args[3]);
}
I don't think this is much better, though, because now
1. Every function has to take (and construct, wastefully) parameters, even if it doesn't need them.
2. If you need to add a function that needs five parameters, you'll have to update the signatures of every single function.

There's just no way around the boilerplate.
I guess you are right. I have one more idea and if it's worse let me know...

Create several maps. 1 Map for 0 parameters, 1 Map for 1 parameters, 1 Map for 2 parameters...Etc...or would that be another mess?
Pages: 12