1
0
Fork 0
mirror of https://gitlab.com/niansa/SomeBot.git synced 2025-03-06 20:48:26 +01:00
SomeBot/LuaBridge3/Source/LuaBridge/detail/Userdata.h
2023-03-02 14:10:47 +01:00

1010 lines
29 KiB
C++

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT
#pragma once
#include "Config.h"
#include "Errors.h"
#include "LuaException.h"
#include "ClassInfo.h"
#include "TypeTraits.h"
#include "Result.h"
#include "Stack.h"
#include <cassert>
#include <stdexcept>
#include <type_traits>
namespace luabridge {
namespace detail {
//=================================================================================================
/**
* @brief Return the identity pointer for our lightuserdata tokens.
*
* Because of Lua's dynamic typing and our improvised system of imposing C++ class structure, there is the possibility that executing
* scripts may knowingly or unknowingly cause invalid data to get passed to the C functions created by LuaBridge.
*
* In particular, our security model addresses the following:
*
* 1. Scripts cannot create a userdata (ignoring the debug lib).
*
* 2. Scripts cannot create a lightuserdata (ignoring the debug lib).
*
* 3. Scripts cannot set the metatable on a userdata.
*/
/**
* @brief Interface to a class pointer retrievable from a userdata.
*/
class Userdata
{
private:
//=============================================================================================
/**
* @brief Validate and retrieve a Userdata on the stack.
*
* The Userdata must exactly match the corresponding class table or const table, or else a Lua error is raised. This is used for the
* __gc metamethod.
*/
static Userdata* getExactClass(lua_State* L, int index, const void* classKey)
{
return (void)classKey, static_cast<Userdata*>(lua_touserdata(L, lua_absindex(L, index)));
}
//=============================================================================================
/**
* @brief Validate and retrieve a Userdata on the stack.
*
* The Userdata must be derived from or the same as the given base class, identified by the key. If canBeConst is false, generates
* an error if the resulting Userdata represents to a const object. We do the type check first so that the error message is informative.
*/
static Userdata* getClass(lua_State* L,
int index,
const void* registryConstKey,
const void* registryClassKey,
bool canBeConst)
{
index = lua_absindex(L, index);
lua_getmetatable(L, index); // Stack: object metatable (ot) | nil
if (!lua_istable(L, -1))
{
lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: registry metatable (rt) | nil
return throwBadArg(L, index);
}
lua_rawgetp(L, -1, getConstKey()); // Stack: ot | nil, const table (co) | nil
assert(lua_istable(L, -1) || lua_isnil(L, -1));
// If const table is NOT present, object is const. Use non-const registry table
// if object cannot be const, so constness validation is done automatically.
// E.g. nonConstFn (constObj)
// -> canBeConst = false, isConst = true
// -> 'Class' registry table, 'const Class' object table
// -> 'expected Class, got const Class'
bool isConst = lua_isnil(L, -1); // Stack: ot | nil, nil, rt
if (isConst && canBeConst)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, registryConstKey); // Stack: ot, nil, rt
}
else
{
lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, co, rt
}
lua_insert(L, -3); // Stack: rt, ot, co | nil
lua_pop(L, 1); // Stack: rt, ot
for (;;)
{
if (lua_rawequal(L, -1, -2)) // Stack: rt, ot
{
lua_pop(L, 2); // Stack: -
return static_cast<Userdata*>(lua_touserdata(L, index));
}
// Replace current metatable with it's base class.
lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil
if (lua_isnil(L, -1)) // Stack: rt, ot, nil
{
// Drop the object metatable because it may be some parent metatable
lua_pop(L, 2); // Stack: rt
return throwBadArg(L, index);
}
lua_remove(L, -2); // Stack: rt, pot
}
// no return
}
static bool isInstance(lua_State* L, int index, const void* registryClassKey)
{
index = lua_absindex(L, index);
int result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nothing
if (result == 0)
return false; // Nothing was pushed on the stack
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // Stack: -
return false;
}
lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, rt
lua_insert(L, -2); // Stack: rt, ot
for (;;)
{
if (lua_rawequal(L, -1, -2)) // Stack: rt, ot
{
lua_pop(L, 2); // Stack: -
return true;
}
// Replace current metatable with it's base class.
lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil
if (lua_isnil(L, -1)) // Stack: rt, ot, nil
{
lua_pop(L, 3); // Stack: -
return false;
}
lua_remove(L, -2); // Stack: rt, pot
}
}
static Userdata* throwBadArg(lua_State* L, int index)
{
assert(lua_istable(L, -1) || lua_isnil(L, -1)); // Stack: rt | nil
const char* expected = 0;
if (lua_isnil(L, -1)) // Stack: nil
{
expected = "unregistered class";
}
else
{
lua_rawgetp(L, -1, getTypeKey()); // Stack: rt, registry type
expected = lua_tostring(L, -1);
}
const char* got = 0;
if (lua_isuserdata(L, index))
{
lua_getmetatable(L, index); // Stack: ..., ot | nil
if (lua_istable(L, -1)) // Stack: ..., ot
{
lua_rawgetp(L, -1, getTypeKey()); // Stack: ..., ot, object type | nil
if (lua_isstring(L, -1))
{
got = lua_tostring(L, -1);
}
}
}
if (!got)
{
got = lua_typename(L, lua_type(L, index));
}
luaL_argerror(L, index, lua_pushfstring(L, "%s expected, got %s", expected, got));
return nullptr;
}
public:
virtual ~Userdata() {}
//=============================================================================================
/**
* @brief Returns the Userdata* if the class on the Lua stack matches.
*
* If the class does not match, a Lua error is raised.
*
* @tparam T A registered user class.
*
* @param L A Lua state.
* @param index The index of an item on the Lua stack.
*
* @return A userdata pointer if the class matches.
*/
template <class T>
static Userdata* getExact(lua_State* L, int index)
{
return getExactClass(L, index, detail::getClassRegistryKey<T>());
}
//=============================================================================================
/**
* @brief Get a pointer to the class from the Lua stack.
*
* If the object is not the class or a subclass, or it violates the const-ness, a Lua error is raised.
*
* @tparam T A registered user class.
*
* @param L A Lua state.
* @param index The index of an item on the Lua stack.
* @param canBeConst TBD
*
* @return A pointer if the class and constness match.
*/
template <class T>
static T* get(lua_State* L, int index, bool canBeConst)
{
if (lua_isnil(L, index))
return nullptr;
return static_cast<T*>(getClass(L,
index,
detail::getConstRegistryKey<T>(),
detail::getClassRegistryKey<T>(),
canBeConst)
->getPointer());
}
template <class T>
static bool isInstance(lua_State* L, int index)
{
return isInstance(L, index, detail::getClassRegistryKey<T>());
}
protected:
Userdata() = default;
/**
* @brief Get an untyped pointer to the contained class.
*/
void* getPointer() const noexcept
{
return m_p;
}
void* m_p = nullptr; // subclasses must set this
};
//=================================================================================================
/**
* @brief Wraps a class object stored in a Lua userdata.
*
* The lifetime of the object is managed by Lua. The object is constructed inside the userdata using placement new.
*/
template <class T>
class UserdataValue : public Userdata
{
public:
UserdataValue(const UserdataValue&) = delete;
UserdataValue operator=(const UserdataValue&) = delete;
~UserdataValue()
{
if (getPointer() != nullptr)
{
getObject()->~T();
}
}
/**
* @brief Push a T via placement new.
*
* The caller is responsible for calling placement new using the returned uninitialized storage.
*
* @param L A Lua state.
*
* @return An object referring to the newly created userdata value.
*/
static UserdataValue<T>* place(lua_State* L, std::error_code& ec)
{
auto* ud = new (lua_newuserdata_x<UserdataValue<T>>(L, sizeof(UserdataValue<T>))) UserdataValue<T>();
lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
ud->~UserdataValue<T>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
ec = makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
return nullptr;
}
lua_setmetatable(L, -2);
return ud;
}
/**
* @brief Push T via copy construction from U.
*
* @tparam U A container type.
*
* @param L A Lua state.
* @param u A container object l-value reference.
* @param ec Error code that will be set in case of failure to push on the lua stack.
*/
template <class U>
static auto push(lua_State* L, const U& u) -> std::enable_if_t<std::is_copy_constructible_v<U>, Result>
{
std::error_code ec;
auto* ud = place(L, ec);
if (!ud)
return ec;
new (ud->getObject()) U(u);
ud->commit();
return {};
}
/**
* @brief Push T via move construction from U.
*
* @tparam U A container type.
*
* @param L A Lua state.
* @param u A container object r-value reference.
* @param ec Error code that will be set in case of failure to push on the lua stack.
*/
template <class U>
static auto push(lua_State* L, U&& u) -> std::enable_if_t<std::is_move_constructible_v<U>, Result>
{
std::error_code ec;
auto* ud = place(L, ec);
if (!ud)
return ec;
new (ud->getObject()) U(std::move(u));
ud->commit();
return {};
}
/**
* @brief Confirm object construction.
*/
void commit() noexcept
{
m_p = getObject();
}
T* getObject() noexcept
{
// If this fails to compile it means you forgot to provide
// a Container specialization for your container!
return reinterpret_cast<T*>(&m_storage);
}
private:
/**
* @brief Used for placement construction.
*/
UserdataValue() noexcept
: Userdata()
{
}
std::aligned_storage_t<sizeof(T), alignof(T)> m_storage;
};
//=================================================================================================
/**
* @brief Wraps a pointer to a class object inside a Lua userdata.
*
* The lifetime of the object is managed by C++.
*/
class UserdataPtr : public Userdata
{
public:
UserdataPtr(const UserdataPtr&) = delete;
UserdataPtr operator=(const UserdataPtr&) = delete;
/**
* @brief Push non-const pointer to object.
*
* @tparam T A user registered class.
*
* @param L A Lua state.
* @param p A pointer to the user class instance.
* @param ec Error code that will be set in case of failure to push on the lua stack.
*/
template <class T>
static Result push(lua_State* L, T* ptr)
{
if (ptr)
return push(L, ptr, getClassRegistryKey<T>());
lua_pushnil(L);
return {};
}
/**
* @brief Push const pointer to object.
*
* @tparam T A user registered class.
*
* @param L A Lua state.
* @param p A pointer to the user class instance.
* @param ec Error code that will be set in case of failure to push on the lua stack.
*/
template <class T>
static Result push(lua_State* L, const T* ptr)
{
if (ptr)
return push(L, ptr, getConstRegistryKey<T>());
lua_pushnil(L);
return {};
}
private:
/**
* @brief Push a pointer to object using metatable key.
*/
static Result push(lua_State* L, const void* ptr, const void* key)
{
auto* udptr = new (lua_newuserdata_x<UserdataPtr>(L, sizeof(UserdataPtr))) UserdataPtr(const_cast<void*>(ptr));
lua_rawgetp(L, LUA_REGISTRYINDEX, key);
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
udptr->~UserdataPtr();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
return throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
return makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
}
lua_setmetatable(L, -2);
return {};
}
explicit UserdataPtr(void* ptr)
{
// Can't construct with a null object!
assert(ptr != nullptr);
m_p = ptr;
}
};
//============================================================================
/**
* @brief Wraps an external value type to a class object inside a Lua userdata.
*
* The lifetime of the object is managed by Lua. The object is constructed inside the userdata using an
* already constructed object provided externally, and it is destructed by a deallocator function provided.
*/
template <class T>
class UserdataValueExternal : public Userdata
{
public:
UserdataValueExternal(const UserdataValueExternal&) = delete;
UserdataValueExternal operator=(const UserdataValueExternal&) = delete;
~UserdataValueExternal()
{
if (getObject() != nullptr)
m_dealloc(getObject());
}
/**
* @brief Push a T via externally allocated object.
*
* @param L A Lua state.
* @param obj The object allocated externally that need to be stored.
* @param dealloc A deallocator function that will free the passed object.
*
* @return An object referring to the newly created userdata value.
*/
template <class Dealloc>
static UserdataValueExternal<T>* place(lua_State* L, T* obj, Dealloc dealloc, std::error_code& ec)
{
auto* ud = new (lua_newuserdata_x<UserdataValueExternal<T>>(L, sizeof(UserdataValueExternal<T>))) UserdataValueExternal<T>(obj, dealloc);
lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
ud->~UserdataValueExternal<T>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
ec = makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
return nullptr;
}
lua_setmetatable(L, -2);
return ud;
}
T* getObject() noexcept
{
return static_cast<T*>(m_p);
}
private:
UserdataValueExternal(void* ptr, void (*dealloc)(T*)) noexcept
{
// Can't construct with a null object!
assert(ptr != nullptr);
m_p = ptr;
// Can't construct with a null deallocator!
assert(dealloc != nullptr);
m_dealloc = dealloc;
}
void (*m_dealloc)(T*) = nullptr;
};
//============================================================================
/**
* @brief Wraps a container that references a class object.
*
* The template argument C is the container type, ContainerTraits must be specialized on C or else a compile error will result.
*/
template <class C>
class UserdataShared : public Userdata
{
public:
UserdataShared(const UserdataShared&) = delete;
UserdataShared& operator=(const UserdataShared&) = delete;
~UserdataShared() = default;
/**
* @brief Construct from a container to the class or a derived class.
*
* @tparam U A container type.
*
* @param u A container object reference.
*/
template <class U>
explicit UserdataShared(const U& u) : m_c(u)
{
m_p = const_cast<void*>(reinterpret_cast<const void*>((ContainerTraits<C>::get(m_c))));
}
/**
* @brief Construct from a pointer to the class or a derived class.
*
* @tparam U A container type.
*
* @param u A container object pointer.
*/
template <class U>
explicit UserdataShared(U* u) : m_c(u)
{
m_p = const_cast<void*>(reinterpret_cast<const void*>((ContainerTraits<C>::get(m_c))));
}
private:
C m_c;
};
//=================================================================================================
/**
* @brief SFINAE helper for non-const objects.
*/
template <class C, bool MakeObjectConst>
struct UserdataSharedHelper
{
using T = std::remove_const_t<typename ContainerTraits<C>::Type>;
static Result push(lua_State* L, const C& c)
{
if (ContainerTraits<C>::get(c) != nullptr)
{
auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);
lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
us->~UserdataShared<C>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
return throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
return makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
}
lua_setmetatable(L, -2);
}
else
{
lua_pushnil(L);
}
return {};
}
static Result push(lua_State* L, T* t)
{
if (t)
{
auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(t);
lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
us->~UserdataShared<C>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
return throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
return makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
}
lua_setmetatable(L, -2);
}
else
{
lua_pushnil(L);
}
return {};
}
};
/**
* @brief SFINAE helper for const objects.
*/
template <class C>
struct UserdataSharedHelper<C, true>
{
using T = std::remove_const_t<typename ContainerTraits<C>::Type>;
static Result push(lua_State* L, const C& c)
{
if (ContainerTraits<C>::get(c) != nullptr)
{
auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);
lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
us->~UserdataShared<C>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
return throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
return makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
}
lua_setmetatable(L, -2);
}
else
{
lua_pushnil(L);
}
return {};
}
static Result push(lua_State* L, T* t)
{
if (t)
{
auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(t);
lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());
if (!lua_istable(L, -1))
{
lua_pop(L, 1); // possibly: a nil
us->~UserdataShared<C>();
#if LUABRIDGE_RAISE_UNREGISTERED_CLASS_USAGE
return throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);
#else
return makeErrorCode(ErrorCode::ClassNotRegistered);
#endif
}
lua_setmetatable(L, -2);
}
else
{
lua_pushnil(L);
}
return {};
}
};
//=================================================================================================
/**
* @brief Pass by container.
*
* The container controls the object lifetime. Typically this will be a lifetime shared by C++ and Lua using a reference count. Because of type
* erasure, containers like std::shared_ptr will not work, unless the type hold by them is derived from std::enable_shared_from_this.
*/
template <class T, bool ByContainer>
struct StackHelper
{
using ReturnType = TypeResult<T>;
static Result push(lua_State* L, const T& t)
{
return UserdataSharedHelper<T, std::is_const_v<typename ContainerTraits<T>::Type>>::push(L, t);
}
static ReturnType get(lua_State* L, int index)
{
using CastType = std::remove_const_t<typename ContainerTraits<T>::Type>;
auto* result = Userdata::get<CastType>(L, index, true);
if (! result)
return makeErrorCode(ErrorCode::InvalidTypeCast);
return ContainerTraits<T>::construct(result);
}
};
/**
* @brief Pass by value.
*
* Lifetime is managed by Lua. A C++ function which accesses a pointer or reference to an object outside the activation record in which it was
* retrieved may result in undefined behavior if Lua garbage collected it.
*/
template <class T>
struct StackHelper<T, false>
{
static Result push(lua_State* L, const T& t)
{
return UserdataValue<T>::push(L, t);
}
static Result push(lua_State* L, T&& t)
{
return UserdataValue<T>::push(L, std::move(t));
}
static TypeResult<std::reference_wrapper<const T>> get(lua_State* L, int index)
{
auto* result = Userdata::get<T>(L, index, true);
if (! result)
return makeErrorCode(ErrorCode::InvalidTypeCast); // nil passed to reference
return std::cref(*result);
}
};
//=================================================================================================
/**
* @brief Lua stack conversions for pointers and references to class objects.
*
* Lifetime is managed by C++. Lua code which remembers a reference to the value may result in undefined behavior if C++ destroys the object.
* The handling of the const and volatile qualifiers happens in UserdataPtr.
*/
template <class C, bool ByContainer>
struct RefStackHelper
{
using ReturnType = TypeResult<C>;
using T = std::remove_const_t<typename ContainerTraits<C>::Type>;
static Result push(lua_State* L, const C& t)
{
return UserdataSharedHelper<C, std::is_const_v<typename ContainerTraits<C>::Type>>::push(L, t);
}
static ReturnType get(lua_State* L, int index)
{
auto* result = Userdata::get<T>(L, index, true);
if (! result)
return makeErrorCode(ErrorCode::InvalidTypeCast);
return ContainerTraits<C>::construct(result);
}
};
template <class T>
struct RefStackHelper<T, false>
{
using ReturnType = TypeResult<std::reference_wrapper<T>>;
static Result push(lua_State* L, const T& t)
{
return UserdataPtr::push(L, std::addressof(t));
}
static ReturnType get(lua_State* L, int index)
{
auto* result = Userdata::get<T>(L, index, true);
if (! result)
return makeErrorCode(ErrorCode::InvalidTypeCast); // nil passed to reference
return std::ref(*result);
}
};
//=================================================================================================
/**
* @brief Trait class that selects whether to return a user registered class object by value or by reference.
*/
template <class T, class Enable = void>
struct UserdataGetter
{
using ReturnType = TypeResult<T*>;
static ReturnType get(lua_State* L, int index)
{
auto* result = Userdata::get<T>(L, index, true);
if (! result)
return makeErrorCode(ErrorCode::InvalidTypeCast);
return result;
}
};
template <class T>
struct UserdataGetter<T, std::void_t<T (*)()>>
{
using ReturnType = TypeResult<T>;
static ReturnType get(lua_State* L, int index)
{
auto result = StackHelper<T, IsContainer<T>::value>::get(L, index);
if (! result)
return result.error();
return *result;
}
};
} // namespace detail
//=================================================================================================
/**
* @brief Lua stack conversions for class objects passed by value.
*/
template <class T, class = void>
struct Stack
{
using IsUserdata = void;
using Getter = detail::UserdataGetter<T>;
using ReturnType = typename Getter::ReturnType;
[[nodiscard]] static Result push(lua_State* L, const T& value)
{
return detail::StackHelper<T, detail::IsContainer<T>::value>::push(L, value);
}
[[nodiscard]] static Result push(lua_State* L, T&& value)
{
return detail::StackHelper<T, detail::IsContainer<T>::value>::push(L, std::move(value));
}
[[nodiscard]] static ReturnType get(lua_State* L, int index)
{
return Getter::get(L, index);
}
[[nodiscard]] static bool isInstance(lua_State* L, int index)
{
return detail::Userdata::isInstance<T>(L, index);
}
};
namespace detail {
//=================================================================================================
/**
* @brief Trait class indicating whether the parameter type must be a user registered class.
*
* The trait checks the existence of member type Stack::IsUserdata specialization for detection.
*/
template <class T, class Enable = void>
struct IsUserdata : std::false_type
{
};
template <class T>
struct IsUserdata<T, std::void_t<typename Stack<T>::IsUserdata>> : std::true_type
{
};
//=================================================================================================
/**
* @brief Trait class that selects a specific push/get implementation for userdata.
*/
template <class T, bool IsUserdata>
struct StackOpSelector;
// pointer
template <class T>
struct StackOpSelector<T*, true>
{
using ReturnType = TypeResult<T*>;
static Result push(lua_State* L, T* value) { return UserdataPtr::push(L, value); }
static ReturnType get(lua_State* L, int index) { return Userdata::get<T>(L, index, false); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
// pointer to const
template <class T>
struct StackOpSelector<const T*, true>
{
using ReturnType = TypeResult<const T*>;
static Result push(lua_State* L, const T* value) { return UserdataPtr::push(L, value); }
static ReturnType get(lua_State* L, int index) { return Userdata::get<T>(L, index, true); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
// l-value reference
template <class T>
struct StackOpSelector<T&, true>
{
using Helper = RefStackHelper<T, IsContainer<T>::value>;
using ReturnType = typename Helper::ReturnType;
static Result push(lua_State* L, T& value) { return UserdataPtr::push(L, &value); }
static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
// l-value reference to const
template <class T>
struct StackOpSelector<const T&, true>
{
using Helper = RefStackHelper<T, IsContainer<T>::value>;
using ReturnType = typename Helper::ReturnType;
static Result push(lua_State* L, const T& value) { return Helper::push(L, value); }
static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }
static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};
} // namespace detail
} // namespace luabridge