Ответ 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) где-нибудь автоматически. Может быть очень сложно, потому что С++ не так просто разобрать.