我可以使用模板(或花哨的宏)将字符串文字可靠地转换为符号名称吗?

And*_*ski 12 c++ templates c++11

一点背景:我想编写一个工具,将一堆命名的东西编译成C++代码.列表发生变化,我不希望在发生这种情况时重建世界.尽管如此,我想通过(文字)名称来解决编译的代码.

作为一个不太正确的事情的例子,我可以把它放在标题中:

template<int name> void func();
Run Code Online (Sandbox Code Playgroud)

然后我的工具可以生成如下代码:

template<> void func<1>() { ... }
template<> void func<2>() { ... }
template<> void func<3>() { ... }
Run Code Online (Sandbox Code Playgroud)

现在我可以在任何地方用"名字"来称呼它们而不预先声明每一个.


我想这样做,但有一些比整数更具描述性的东西.理想情况下,我想要某种形式的文本.我需要的是:

#define FUNC_WITH_NAME(name) func_named_ ## name
Run Code Online (Sandbox Code Playgroud)

但这并不完全有效:它需要声明func_named_whatever.

下一次尝试也不好(并且它是特定于GCC的):

#define FUNC_WITH_NAME(name) ({extern void func_named_ ## name; func_named_ ## name;})
Run Code Online (Sandbox Code Playgroud)

它失败了,因为如果它在命名空间中使用,那么它最终会在该命名空间中查找func_named_whatever .

我想出的最好的是:

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

namespace NS {

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

}
Run Code Online (Sandbox Code Playgroud)

这是有效的,但它很难看(如果不跳过令人讨厌的篮球,如何将字符串文字转换为参数包并不明显).此外,如果符号没有解析,那么来自g ++的错误消息很糟糕:

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
Run Code Online (Sandbox Code Playgroud)

我唯一想到的是gcc扩展:

extern void func_named_whatever __asm__("func_named_whatever");
Run Code Online (Sandbox Code Playgroud)

但这不是一个好的模板参数(它只影响对该函数的调用;它不会影响使用魔法asm -ified符号,当它们是模板参数时),并且它会因任何链接时类型检查而失败,因为它会关闭截断.

Sig*_*erm 5

现在我可以在任何地方通过“名称”来调用它们,而无需预先声明每一个。

要在编译时调用任何函数,您需要前向声明它。因为您想在编译时调用它们,所以不需要使用字符串文字。而且您只能使用预处理器而不是模板来执行此操作,因为您无法为模板指定标识符名称(至少在 C++03 中)。

例子:

#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;
}
Run Code Online (Sandbox Code Playgroud)

您可以在函数体内前向声明函数:

#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;
}
Run Code Online (Sandbox Code Playgroud)

因此,从技术上讲,您甚至不需要为此使用预处理器。

您无法避免前向声明,除非所有函数参数及其类型都已知,在这种情况下,您可以使用宏隐藏前向声明。

#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;
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您想在每次调用函数时指定函数参数类型并知道参数数量:

#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;
}
Run Code Online (Sandbox Code Playgroud)

或者如果你的函数可以接受可变数量的参数。(解析可变参数很有趣):

#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;
}
Run Code Online (Sandbox Code Playgroud)

如果您想使用字符串(如“func1”中),那么您将尝试在运行时而不是编译时定位函数,即使您实际上并不这么认为。这是因为"funcname"与 () 没有什么不同std::string(std::string("func") + std::string("name")).c_str()- 它是指向带有字符的内存区域的指针。某些编译器可能提供“unstringize”字符串的扩展,但我不知道此类扩展。

在这种情况下,您唯一的选择是编写预处理器或代码生成器,每次构建项目时都会扫描某种文本模板(列出函数),并将其转换为.h/ .cpp 文件,然后编译到您的项目中。这些 .h/.cpp 文件应该构建函数表(名称到函数指针映射),然后在项目中“在幕后”使用。请参阅Qt MOC以获取工作示例。每次向模板添加新函数时都需要重新编译。

如果您不希望对每个新函数原型进行重新编译(显然,尽管您无法在不重新编译项目的情况下添加对新函数的调用),那么您唯一的选择是将脚本语言嵌入到您的应用程序中。这样您就可以添加功能而无需重新编译。现在,您可以嵌入lua、python、lisp(通过ecl)和其他语言。还有可用的C++ 解释器,尽管我怀疑它是否可嵌入。

如果您不想使用我列出的任何选项,那么(据我所知)您根本不能这样做。删除一些要求(“无重新编译”、“无前向声明”、“使用字符串文字调用”),然后重试。


我可以使用 C 宏语言可靠地将字符串文字转换为符号名称吗?

不可以。您可以将字符串文字转换为要由编译器处理的标识符(使用 stringize),但如果编译器在编译时不知道该标识符,则您的代码将无法编译。因此,如果您要使用函数的名称以这种方式调用函数,那么您必须确保它们之前都已被前向声明。而且您将无法在运行时找到它们。


C++ 不在编译代码中存储函数和变量的名称。所以你无法通过名称找到已编译的函数。这是因为 C++ 链接器可以自由地完全消除未使用的函数、内联它们或创建多个副本。

你可以做什么:

  1. 创建一个要按名称寻址的函数表(将函数名称映射到函数指针),然后使用该表来定位函数。您必须手动注册您希望能够在此表中找到的每个函数。像这样的东西:

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用动态/共享库。将您希望能够寻址的函数放入共享库(extern "C" __declspec(dllexport)__declspec(dllexport))中,将它们标记为导出,然后使用操作系统函数来定位库中的函数(dlsym在Linux上,GetProcAddress在Windows上)。Afaik,您也可以从 exe 导出函数,因此您可以使用这种方法而无需额外的 dll。

  3. 将脚本语言嵌入到您的应用程序中。基本上,在大多数脚本语言中,您可以通过名称来定位和调用函数。显然,这将是在脚本语言中声明的函数,而不是 C++ 函数。
  4. 编写代码预处理器,它将扫描项目中的“命名”函数,并在某处自动构建这些函数(方法#1)的表。可能非常困难,因为 C++ 并不那么容易解析。