Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I reliably turn a string literal into a symbol name using templates (or fancy macros)?

A bit of background: I want to write a tool that compiles a bunch of named things into C++ code. The list changes and I don't want to rebuild the world when that happens. Despite that, I want to address the compiled code by (literal) name.

As an example of something that's not quite right, I could have put this in a header:

template<int name> void func();

Then my tool can generate code like:

template<> void func<1>() { ... }
template<> void func<2>() { ... }
template<> void func<3>() { ... }

Now I can call these by "name" anywhere without pre-declaring each one.


I want to do this, but with something more descriptive than integers. Ideally I want text of some form. What I need is something like:

#define FUNC_WITH_NAME(name) func_named_ ## name

That doesn't quite work, though: it needs a declaration of func_named_whatever.

The next try is no good either (and it's GCC-specific):

#define FUNC_WITH_NAME(name) ({extern void func_named_ ## name; func_named_ ## name;})

It fails because, if it's used inside a namespace, then it ends up looking for func_named_whatever in that namespace.

The best I've come up with is this:

template<char... tagchars> int tagged();

namespace NS {

  int caller()
  {
    return tagged<'n', 'a', 'm', 'e'>();
  }

}

This works, but it's ugly (and it's not obvious how to turn a string literal into a parameter pack without jumping through nasty hoops). Also, if the symbol doesn't resolve, then the error message from g++ is terrible:

In function `NS::caller()':
named_symbol.cpp:(.text+0x5): undefined reference to `int tagged<(char)110, (char)97, (char)109, (char)101>()'
collect2: error: ld returned 1 exit status

The only thing that I've come up with is a gcc extension:

extern void func_named_whatever __asm__("func_named_whatever");

But this is no good as a template argument (it only affects calls to that function; it does not affect use of magic asm-ified symbols when they're template arguments), and it defeats any link-time type checking because it turns off mangling.

like image 647
Andy Lutomirski Avatar asked Jun 21 '13 00:06

Andy Lutomirski


People also ask

How do you make a string literal?

A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string. You must always prefix wide-string literals with the letter L.

How do you define a string macro?

We can create two or more than two strings in macro, then simply write them one after another to convert them into a concatenated string. The syntax is like below: #define STR1 "str1" #define STR2 " str2" #define STR3 STR1 STR2 //it will concatenate str1 and str2. Input: Take two strings.

What does ## mean in macro?

The double-number-sign or token-pasting operator (##), which is sometimes called the merging or combining operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token, and therefore, can't be the first or last token in the macro definition.

What is stringizing operator?

Stringizing operator (#) The number-sign or "stringizing" operator (#) converts macro parameters to string literals without expanding the parameter definition. It's used only with macros that take arguments.


1 Answers

Now I can call these by "name" anywhere without pre-declaring each one.

To call any function at compile time, you need to forward-declare it. Because you want to call them at compile time, there's no need to use string literals. And you can only do this using preprocessor, not templates, because you cannot specify identifier names for templates (in C++03, at least).

Example:

#include <iostream>

#define CALL_FUNC(func, args) name_ ##func args;

void name_func1(){
    std::cout << "func1" << std::endl;
}

void name_func2(int a){
    std::cout << "func2:" << a << std::endl;
}

int main(int argc, char** argv){
    CALL_FUNC(func1, ());
    CALL_FUNC(func2, (46));
    return 0;
}

You can forward-declare function within function body:

#include <iostream>

int main(int argc, char** argv){
    void name_func(int);
    name_func(42);
    return 0;
}

void name_func(int arg){
    std::cout << "func1:" << arg << std::endl;
}

So, technically, you don't even need to use preprocessor for that.

You cannot avoid forward-declaration, unless all functions arguments are known as well as their types, in which case you can hide forward-declaration with macros.

#include <iostream>

#define FUNC_NAME(func) name_ ##func
#define CALL_VOID_FUNC(func) { void FUNC_NAME(func)(); FUNC_NAME(func)(); }

int main(int argc, char** argv){
    CALL_VOID_FUNC(func1);//not forward declared
    return 0;
}


void name_func1(){
    std::cout << "func1" << std::endl;
}

Or if you want to specify function argument types every time you call functions and know number of arguments:

#include <iostream>

#define FUNC_NAME(func) name_ ##func
#define CALL_FUNC_1ARG(func, type1, arg1) { void FUNC_NAME(func)(type1); FUNC_NAME(func)(arg1); }

int main(int argc, char** argv){
    CALL_FUNC_1ARG(func1, int, 42);
    return 0;
}


void name_func1(int arg){
    std::cout << "func1:" << arg << std::endl;
}

Or if your function can take variable number of arguments. (parsing varargs is fun):

#include <iostream>

#define FUNC_NAME(func) name_ ##func
#define CALL_FUNC_VARIADIC(func, args) { void FUNC_NAME(func)(...); FUNC_NAME(func)args; }

int main(int argc, char** argv){
    CALL_FUNC_VARIADIC(func1, (42, 43, 44));
    return 0;
}


void name_func1(...){
    //std::cout << "func1:" << arg << std::endl;
}

If you want to use STRINGS (as in "func1"), then you are trying to locate function at run time, not at compile time, even if you don't really think so. That's because "funcname" isn't that different from (std::string(std::string("func") + std::string("name")).c_str()) - it is pointer to memory region with character. Some compiler might provide extensions to "unstringize" string, but I'm not aware of such extensions.

In this case your only option is to write either preprocessor or code-generator, that'll scan some kind of text template (that lists functions) every time you build the project, and converts it into .h/.cpp files that are then compiled into your project. Those .h/.cpp files shoudl build function table (name to function pointer map) that is then used "behind the scenes" in your project. See Qt MOC for a working example. That'll require recompilation every time you add new function to template.

If you do not want recompilation for every new function prototype (although you can't add call to a new function without recompiling project, obviously), then your only choice is to embed scripting language into your application. This way you'll be able to add functions without recompiling. At we momen, you can embed lua, python, lisp(via ecl) and other languages. There's also working C++ interpreter, although I doubt it is embeddable.

If you do not want to use any options I listed, then (AFAIK) you cannot do that at all. Drop some requirement ("no recompilation", "no forward declaration", "call using string literal") and try again.


Can I reliably turn a string literal into a symbol name using the C macro language?

No. You can turn string literal into identifier to be processed by compiler (using stringize), but if compiler doesn't know this identifier at this point of compilation, your code won't compile. So, if you're going to call functions this way using their names, then you'll have to insure that they all were forward-declared before. And you won't be able to locate them at runtime.


C++ doesn't store names for functions and variables in compiled code. So you can't find compiled function by its name. This is because C++ linker is free to eliminate unused functions completely, inline them or create multiple copies.

What you CAN do:

  1. Create a table of functions that you want to address by name (that maps function name to function pointer), then use this table to locate functions. You'll have to manually register every function you want to be able to find in this table. Something like this:

    typedef std::string FunctionName;
    
    typedef void(*Function)(int arg);
    
    typedef std::map<FunctionName, Function> FunctionMap;
    
    FunctionMap globalFunctionMap;
    void callFunction(const std::string &name, int arg){
         FunctionMap::iterator found = globalFunctionMap.find(name);
         if (found == globalFunctionMap.end()){
              //could not find function
              return;
         }   
         (*found->second)(arg);
    }
    
  2. Use dynamic/shared libraries. Put functions you want to be able to address into shared library (extern "C" __declspec(dllexport) or __declspec(dllexport)), mark them for export then use operating system functions to locate function within library (dlsym on linux, GetProcAddress of windows). Afaik, you might be able export functions from exe as well, so you might be able to use this approach without additional dlls.

  3. Embed scripting language into your application. Basically, in most scripting languages you can locate and call function by its name. That'll be function declared within scripting language, obviously, not a C++ function.
  4. Write code preprocessor that'll scan your project for "named" functions and build table of those function (method #1) somewhere automatically. Can be very difficult, because C++ is not that easy to parse.
like image 152
SigTerm Avatar answered Sep 30 '22 02:09

SigTerm