Я пишу оболочку для встроенного Lua, и у меня есть ряд функций для получения глобальных значений из lua_State. Поскольку функция делает почти одно и то же для каждого (получает глобальное имя с помощью lua_getglobal(L, name), вызывает соответствующую функцию lua_to___(), а затем выталкивает стек, чтобы вернуть lua_State в исходное состояние), я решил, что должен быть какой-то способ сделать это с шаблонами.
Однако я не могу найти способ, чтобы эта одна конкретная строка, в которой тип имеет значение, зависела от типа, без написания полностью отдельных функций для каждого типа. Хотя сейчас эта функция будет состоять всего из трех строк, существуют другие похожие функции, которые могут быть более сложными, но с той же проблемой.
На данный момент функции выглядят следующим образом (это в классе LuaManager, который имеет член lua_State *):
//Declaration
template<std::string>
std::string GetGlobal(const std::string & name);
template<int>
int GetGlobal(const std::string & name);
template<float>
float GetGlobal(const std::string & name);
template<bool>
bool GetGlobal(const std::string & name);
//Implementation
template<typename T>
T LuaManager::GetGlobal(const std::string & name)
{
lua_getglobal(luaState, name.c_str()); //push the global to the top of the stack
T value = lua_to____(luaState, -1); //store the value for return
lua_pop(luaState, 1); //return the stack to empty
return value;
}
Есть ли способ специализировать шаблон для отдельных строк кода? Или я неправильно понимаю, для чего мне следует использовать шаблоны?
@ user463035818 Вызывающий будет использовать GetGlobal <int> (name) для получения int, GetGlobal <float> для получения числа с плавающей запятой и т. д.
хорошо, тогда вы почти у цели. дайте мне секунду, чтобы написать ответ;)
Вопрос не помечен как C++ 17, но, может быть, у вас все еще есть доступ к функциям C++ 17?
@sebrockm Я не уверен. У меня определенно есть функции C++ 11, но я не уверен, что есть в VS2017.
извините, неправильно прочитал вопрос, придется исправить мой уже написанный ответ, но сейчас нет времени





Объявление должно быть таким:
template<class T>
T GetGlobal(const std::string& name);
А для реализации я бы создал шаблон структуры и использовал бы специализации как карту от типа к функции.
#include <type_traits>
template<class>
struct lua_to;
template<>
struct lua_to<int> {
typedef int(*type)(decltype(luaState), int);
static constexpr const type value = lua_to_int;
};
template<>
struct lua_to<std::string> {
typedef std::string(*type)(decltype(luaState), int);
static constexpr const type value = lua_to_string;
};
// In this case, since this is so tedious, I would use a macro
#define MY_MODULE_DEFINE_LUA_TO(ctype, luatype) \
template<> \
struct lua_to<ctype> { \
typedef ctype(*type)(decltype(luaState), int); \
static constexpr const type value = lua_to_ ## luatype; \
};
MY_MODULE_DEFINE_LUA_TO(std::map, table);
MY_MODULE_DEFINE_LUA_TO(double, float);
#undef MY_MODULE_DEFINE_LUA_TO
template<class T>
T GetGlobal(const std::string& name) {
lua_getglobal(luaState, name); //push the global to the top of the stack
T value = lua_to<T>::value(luaState, -1); //store the value for return
lua_pop(luaState, 1); //return the stack to empty
return value;
}
Это может быть менее утомительно с шаблонами переменных, но это правильный подход :)
Если ваш компилятор поддерживает C++ 17, вы можете использовать if constexpr:
template<typename T>
T LuaManager::GetGlobal(const std::string & name)
{
lua_getglobal(luaState, name);
T value;
if constexpr (std::is_same_v<T, std::string>)
value = lua_to_string(luaState, -1); // I don't know the actual name of this function
else if (std::is_same_v<T, int>)
value = lua_to_int(luaState, -1);
else if (std::is_same_v<T, whatever>)
value = lua_to_whatever(luaState, -1);
// some other arbitrary type dependent code
else ... // other types
lua_pop(luaState, 1);
return value;
}
Примечание. Чтобы включить C++ 17 в Visual Studio, щелкните свой проект правой кнопкой мыши и выберите «Свойства». Затем перейдите в C / C++ -> Язык -> Стандарт языка C++ и выберите /std:c++17 или /std:c++latest.
Если вы не можете или не хотите использовать C++ 17, вот другой подход, который не использует никаких «новых» функций, даже без шаблонов:
void get_lua_value(string& value)
{
value = lua_to_string(luaState, -1);
}
void get_lua_value(int& value)
{
value = lua_to_int(luaState, -1);
}
Добавьте по одной из этих перегрузок для каждого типа. Затем вы можете просто вызвать get_lua_value(), и разрешение перегрузки сделает всю работу за вас:
template<typename T>
T LuaManager::GetGlobal(const std::string& name)
{
lua_getglobal(luaState, name);
T value;
get_lua_value(value);
lua_pop(luaState, 1);
return value;
}
Я думаю, в этом случае у вас должна быть общая декларация и специализированные реализации. Вы не можете просто «переключить функцию» на основе T в этой единственной строке. Рассмотрим этот пример:
#include <iostream>
// Generic declaration
template <typename T>
T doStuff(int arg);
// Specific definitions
template<>
int doStuff(int arg){
return arg + 1;
}
template<>
float doStuff(int arg){
return arg - 1;
}
int main(){
// Our templated function varies in return type,
// so you always have to explicitly specify which variant to use
// This WOULD NOT let compile infer what you want:
/* float test = doStuff(10) */ // ambiguous call
// This is OK
std::cout << doStuff<int>(10) << " " << doStuff<float>(10) << "\n";
return 0;
}
У вас будет почти идентичная функция GetGlobal, отличающаяся только одной строчкой. Если вы хотите сократить количество повторений, у вас может быть шаблон для преобразования только в тип C++ (принимая luaState в качестве аргумента), а затем, возможно, шаблон GetGlobal, который вызывает соответствующий вариант шаблона
Оказывается, я пытался сделать это немного сложнее, чем мне нужно: Я сделал функцию GetGlobal совершенно не заботящейся о том, какой тип она пытается получить:
template<typename T>
T GetGlobal(const std::string & name)
{
lua_getglobal(luaState, name.c_str());
T value = lua_to<T>(-1);
lua_pop(luaState, 1);
return value;
}
Затем я определил шаблон для lua_to<T>(int stack_index) и сделал каждую специализацию для него отдельной функцией:
template<typename T>
T lua_to(int stack_index);
template<>
int lua_to(int stack_index) {
return lua_tointeger(luaState, stack_index);
}
template<>
std::string lua_to(int stack_index) {
return std::string(lua_tostring(luaState, stack_index));
}
Пока он работает как для std::string, так и для int, что, похоже, означает, что он будет работать и для других типов.
чего ты хочешь добиться? Звонящий должен выбрать тип? Или вызывающий должен просто передать
nameглобального и вернуть правильный тип?