13

一点背景知识:我想编写一个工具,将一堆命名的东西编译成 C++ 代码。列表发生了变化,我不想在发生这种情况时重建世界。尽管如此,我还是想通过(字面)名称来处理编译后的代码。

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

template<int name> void func();

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

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

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


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

#define FUNC_WITH_NAME(name) func_named_ ## name

但这并不完全奏效:它需要一个 func_named_whatever 的声明。

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

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

它失败是因为,如果它在命名空间内使用,那么它最终会在该命名空间中寻找 func_named_whatever 。

我想出的最好的是:

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

namespace NS {

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

}

这行得通,但它很丑(而且如何将字符串文字转换为参数包而不跳过讨厌的箍并不明显)。此外,如果符号无法解析,则来自 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

我唯一想到的是一个 gcc 扩展:

extern void func_named_whatever __asm__("func_named_whatever");

但这作为模板参数不好(它只影响对该函数的调用;当它们是模板参数时,它不会影响魔术asm化符号的使用),并且它会破坏任何链接时类型检查,因为它会关闭破坏。

4

2 回答 2

4

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

要在编译时调用任何函数,您需要前向声明它。因为您想在编译时调用它们,所以不需要使用字符串文字。而且您只能使用预处理器而不是模板来执行此操作,因为您不能为模板指定标识符名称(至少在 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;
}

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

#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;
}

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

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

#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;
}

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

#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;
}

或者,如果您的函数可以采用可变数量的参数。(解析可变参数很有趣):

#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;
}

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

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

如果您不想为每个新函数原型重新编译(尽管显然您不能在不重新编译项目的情况下添加对新函数的调用),那么您唯一的选择是将脚本语言嵌入到您的应用程序中。这样您就可以在不重新编译的情况下添加函数。目前,您可以嵌入 lua、python、lisp(via ecl) 和其他语言。还有工作C++ 解释器,虽然我怀疑它是可嵌入的。

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


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

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

理想的解决方案是N3413,但这还有很长的路要走。

感谢0x499602d2Using strings in C++ template metaprograms,这是一个马马虎虎的答案:

template<char... str>
struct tag
{
  template<char first>
  struct prepend
  {
    typedef tag<first, str...> type;
  };
};

template<typename Tag>
void func();

#define PREPARE_STR_TAGGER(str)                                         \
  template<int charsleft>                                               \
  struct tagger_for_##str                                               \
  {                                                                     \
    typedef typename                                                    \
      tagger_for_##str<charsleft-1>::type::                             \
      template prepend<(#str)[sizeof(#str)-1-charsleft]>::type type;    \
  };                                                                    \
  template<>                                                            \
  struct tagger_for_##str<0>                                            \
  {                                                                     \
    typedef tag<> type;                                                 \
  };

#define STRING_TO_TAG(str) tagger_for_##str<sizeof(#str)-1>::type

namespace SHOULD_NOT_MATTER {

  PREPARE_STR_TAGGER(some_string);

  void test()
  {
    func<STRING_TO_TAG(some_string)>();
  }

}

缺点:

  • 使用起来很尴尬:您需要在命名空间(或类)范围内使用 PREPARE_STR_TAGGER。
  • 编译时间可能不友好。
  • 它生成的链接器错误非常糟糕。

某种基于 constexpr 的像样的散列函数会起作用,但它会导致更糟糕的错误消息。

欢迎改进。

于 2013-06-21T23:33:10.080 回答