Могу ли я надежно превратить строковый литерал в имя символа с помощью шаблонов (или причудливых макросов)?

Немного фона: я хочу написать инструмент, который компилирует кучу названных вещей в код на С++. Список меняется, и я не хочу восстанавливать мир, когда это произойдет. Несмотря на это, я хочу обратиться к скомпилированному коду с помощью (буквального) имени.

В качестве примера чего-то, что не совсем правильно, я мог бы поместить это в заголовок:

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 -сертифицированных символов, когда они являются аргументами шаблона), и он побеждает любая проверка времени соединения, поскольку она отключает манипуляцию.

Ответ 1

Теперь я могу называть их "именем" в любом месте без предварительного объявления каждого из них.

Чтобы вызвать любую функцию во время компиляции, вам нужно отправить ее вперед. Поскольку вы хотите вызвать их во время компиляции, нет необходимости использовать строковые литералы. И вы можете сделать это только с помощью препроцессора, а не шаблонов, потому что вы не можете указывать имена идентификаторов для шаблонов (по крайней мере, на С++ 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;
}

Или, если ваша функция может принимать переменное количество аргументов. (разбор varargs - забава):

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

Если вы хотите использовать STRINGS (как в "func1" ), вы пытаетесь найти функцию во время выполнения, а не во время компиляции, даже если вы на самом деле не так думаете. Это потому, что "funcname" не отличается от (std::string(std::string("func") + std::string("name")).c_str()) - это указатель на область памяти с символом. Некоторый компилятор может предоставить расширения для строки "unstringize", но я не знаю о таких расширениях.

В этом случае ваш единственный вариант - написать либо препроцессор, либо генератор кода, который будет сканировать какой-то текстовый шаблон (который перечисляет функции) каждый раз при создании проекта и преобразует его в файлы .h/.cpp которые затем скомпилированы в ваш проект. Эти .h/.cpp файлы shoudl build function table (name to function pointer map), которые затем используются "за кадром" в вашем проекте. См. Qt MOC для рабочего примера. Это потребует перекомпиляции при каждом добавлении новой функции в шаблон.

Если вы не хотите перекомпиляции для каждого нового прототипа функции (хотя вы не можете добавить вызов новой функции без повторной компиляции проекта, очевидно), тогда ваш единственный выбор - встроить скриптовый язык в ваше приложение. Таким образом, вы сможете добавлять функции без перекомпиляции. В то же время вы можете встраивать lua, python, lisp (через ecl) и другие языки. Там также работает интерпретатор С++, хотя я сомневаюсь, что он встраивается.

Если вы не хотите использовать какие-либо опции, перечисленные мной, то (AFAIK) вы не можете этого сделать вообще. Отбросьте некоторые требования ( "нет перекомпиляции", "нет прямого объявления", "вызов с использованием строкового литерала" ) и повторите попытку.


Могу ли я надежно преобразовать строковый литерал в имя символа с использованием макроса языка C?

Нет. Вы можете превратить строковый литерал в идентификатор, который будет обрабатываться компилятором (используя stringize), но если компилятор не знает этот идентификатор в этой точке компиляции, ваш код не будет компилироваться. Итак, если вы собираетесь называть функции таким образом, используя их имена, тогда вам нужно будет гарантировать, что все они были заранее объявлены. И вы не сможете найти их во время выполнения.


С++ не сохраняет имена для функций и переменных в скомпилированном коде. Таким образом, вы не можете найти скомпилированную функцию по ее имени. Это связано с тем, что С++-линкером можно полностью исключить неиспользуемые функции, встроить их или создать несколько копий.

Что вы можете сделать:

  • Создайте таблицу функций, которую вы хотите адресовать по имени (которая сопоставляет имя функции с указателем функции), а затем используйте эту таблицу для поиска функций. Вам придется вручную зарегистрировать каждую функцию, которую вы хотите найти в этой таблице. Что-то вроде этого:

    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);
    }
    
  • Использовать динамические/разделяемые библиотеки. Поместите функции, которые вы хотите адресовать в общую библиотеку (extern "C" __declspec(dllexport) или __declspec(dllexport)), пометите их для экспорта, затем используйте функции операционной системы, чтобы найти функцию в библиотеке (dlsym в linux, GetProcAddress для окон). Afaik, вы также можете экспортировать функции из exe, поэтому вы можете использовать этот подход без дополнительных DLL.

  • Вставьте скриптовый язык в свое приложение. В основном, на большинстве языков сценариев вы можете найти и вызвать функцию по ее имени. Это будет функция, объявленная на языке сценариев, очевидно, не как функция С++.
  • Запишите препроцессор кода, который сканирует ваш проект на "именованные" функции и создаст таблицу этой функции (метод # 1) где-нибудь автоматически. Может быть очень сложно, потому что С++ не так просто разобрать.

Ответ 2

Идеальное решение было бы N3413, но это было далеко.

Благодаря 0x499602d2 и Использование строк в метапрограммах шаблонов С++, вот так ответ:

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)>();
  }

}

Downsides:

  • Неловко использовать: вам нужно использовать PREPARE_STR_TAGGER в области пространства имен (или, возможно, класса).
  • Вероятно, нечестно компилировать время.
  • Ошибки компоновщика, которые он генерирует, ужасны.

Какой-то достойный хэш-функция, основанная на constexpr, будет работать, но это приведет к еще более ужасным сообщениям об ошибках.

Усовершенствования приветствуются.