less bin size nameof & nameof_type
This commit is contained in:
parent
48ffc2f855
commit
6a976ef472
2 changed files with 179 additions and 58 deletions
|
@ -27,18 +27,6 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
std::string operator+(std::string_view lhs, std::string_view rhs) {
|
|
||||||
return std::string{lhs.data(), lhs.length()}.append(rhs.data(), rhs.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr long double operator"" _deg(long double deg) {
|
|
||||||
return deg * 3.141592 / 180.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string operator"" _string(const char* str, std::size_t) {
|
|
||||||
return std::string{str};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SomeStruct {
|
struct SomeStruct {
|
||||||
int somefield = 0;
|
int somefield = 0;
|
||||||
|
|
||||||
|
@ -53,7 +41,18 @@ void SomeMethod3() {
|
||||||
|
|
||||||
template <typename T, typename U>
|
template <typename T, typename U>
|
||||||
std::string SomeMethod4(U value) {
|
std::string SomeMethod4(U value) {
|
||||||
return NAMEOF(SomeMethod4<T, U>) + "<" + NAMEOF_TYPE(T) + ", " + NAMEOF_TYPE(U) + ">(" + NAMEOF_TYPE(U) + " " + NAMEOF(value) + ")";
|
auto function_name = std::string{NAMEOF(SomeMethod4<T, U>)}
|
||||||
|
.append("<")
|
||||||
|
.append(NAMEOF_TYPE(T))
|
||||||
|
.append(", ")
|
||||||
|
.append(NAMEOF_TYPE(U))
|
||||||
|
.append(">(")
|
||||||
|
.append(NAMEOF_TYPE(U))
|
||||||
|
.append(" ")
|
||||||
|
.append(NAMEOF(value).data())
|
||||||
|
.append(")");
|
||||||
|
|
||||||
|
return function_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -89,11 +88,13 @@ int main() {
|
||||||
constexpr auto name = NAMEOF(structvar);
|
constexpr auto name = NAMEOF(structvar);
|
||||||
static_assert("structvar" == name);
|
static_assert("structvar" == name);
|
||||||
|
|
||||||
|
#if defined(__clang__) || defined(__GNUC__) && __GNUC__ >= 9 || defined(_MSC_VER)
|
||||||
// Nameof enum variable.
|
// Nameof enum variable.
|
||||||
auto color = Color::RED;
|
auto color = Color::RED;
|
||||||
std::cout << nameof::nameof_enum(color) << std::endl; // 'RED'
|
std::cout << nameof::nameof_enum(color) << std::endl; // 'RED'
|
||||||
std::cout << NAMEOF_ENUM(color) << std::endl; // 'RED'
|
std::cout << NAMEOF_ENUM(color) << std::endl; // 'RED'
|
||||||
std::cout << nameof::nameof_enum<Color::GREEN>() << std::endl; // 'GREEN'
|
std::cout << nameof::nameof_enum<Color::GREEN>() << std::endl; // 'GREEN'
|
||||||
|
#endif
|
||||||
|
|
||||||
// Nameof.
|
// Nameof.
|
||||||
std::cout << NAMEOF(structvar) << std::endl; // 'structvar'
|
std::cout << NAMEOF(structvar) << std::endl; // 'structvar'
|
||||||
|
@ -129,7 +130,6 @@ int main() {
|
||||||
|
|
||||||
// Nameof macro.
|
// Nameof macro.
|
||||||
std::cout << NAMEOF(__LINE__) << std::endl; // '__LINE__'
|
std::cout << NAMEOF(__LINE__) << std::endl; // '__LINE__'
|
||||||
std::cout << NAMEOF(NAMEOF(structvar)) << std::endl; // 'NAMEOF'
|
|
||||||
|
|
||||||
// Nameof raw.
|
// Nameof raw.
|
||||||
std::cout << NAMEOF_RAW(structvar.somefield) << std::endl; // 'structvar.somefield'
|
std::cout << NAMEOF_RAW(structvar.somefield) << std::endl; // 'structvar.somefield'
|
||||||
|
@ -141,7 +141,7 @@ int main() {
|
||||||
|
|
||||||
auto div = [](int x, int y) -> int {
|
auto div = [](int x, int y) -> int {
|
||||||
if (y == 0) {
|
if (y == 0) {
|
||||||
throw std::invalid_argument(NAMEOF(y) + " should not be zero!");
|
throw std::invalid_argument(std::string{NAMEOF(y)} + " should not be zero!");
|
||||||
}
|
}
|
||||||
return x / y;
|
return x / y;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <iosfwd>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
@ -100,35 +101,146 @@ struct nameof_enum_supported final
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <std::size_t N>
|
template <std::size_t N>
|
||||||
struct static_string final {
|
struct [[nodiscard]] static_string final {
|
||||||
constexpr static_string(std::string_view str) noexcept : static_string(str, std::make_index_sequence<N>{}) {}
|
constexpr static_string(std::string_view str) noexcept : static_string{str, std::make_index_sequence<N>{}} {}
|
||||||
|
|
||||||
constexpr operator std::string_view() const noexcept { return {chars.data(), chars.size()}; }
|
[[nodiscard]] constexpr auto data() const noexcept { return chars.data(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto size() const noexcept { return N; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto begin() const noexcept { return data(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto end() const noexcept { return data() + size(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto cbegin() const noexcept { return begin(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto cend() const noexcept { return end(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto rbegin() const noexcept { return std::reverse_iterator<decltype(end())>{end()}; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto rend() const noexcept { return std::reverse_iterator<decltype(begin())>{begin()}; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto crbegin() const noexcept { return rbegin(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto crend() const noexcept { return rend(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto operator[](std::size_t i) const noexcept { return chars[i]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto at(std::size_t i) const { return chars.at(i); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto front() const noexcept { return chars[0]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto back() const noexcept { return chars[N]; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto length() const noexcept { return size(); }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto empty() const noexcept { return false; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr auto compare(std::string_view str) const noexcept {
|
||||||
|
return std::string_view{data(), size()}.compare(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr operator std::string_view() const noexcept { return {data(), size()}; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <std::size_t... I>
|
template <std::size_t... I>
|
||||||
constexpr static_string(std::string_view str, std::index_sequence<I...>) noexcept : chars{{str[I]...}} {}
|
constexpr static_string(std::string_view str, std::index_sequence<I...>) noexcept : chars{{str[I]..., '\0'}} {}
|
||||||
|
|
||||||
const std::array<char, N> chars;
|
const std::array<char, N + 1> chars;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct static_string<0> final {
|
struct static_string<0> final {
|
||||||
constexpr static_string(std::string_view) noexcept {}
|
constexpr static_string(std::string_view) noexcept {}
|
||||||
|
|
||||||
|
constexpr const char* data() const noexcept { return nullptr; }
|
||||||
|
|
||||||
|
constexpr std::size_t size() const noexcept { return 0; }
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr int compare(std::string_view str) const noexcept {
|
||||||
|
return std::string_view{}.compare(str);
|
||||||
|
}
|
||||||
|
|
||||||
constexpr operator std::string_view() const noexcept { return {}; }
|
constexpr operator std::string_view() const noexcept { return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator==(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator==(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator!=(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator>(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator>(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator>=(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator>=(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator<(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator<(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator<=(const static_string<N>& lhs, std::string_view rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
[[nodiscard]] constexpr bool operator<=(std::string_view lhs, const static_string<N>& rhs) noexcept {
|
||||||
|
return lhs.compare(rhs) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename Traits, std::size_t N>
|
||||||
|
std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& os, const static_string<N>& srt) {
|
||||||
|
for (auto c : std::string_view{srt}) {
|
||||||
|
os.put(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||||
|
|
||||||
template <typename T, typename R>
|
template <typename T, typename R>
|
||||||
using enable_if_enum_t = std::enable_if_t<std::is_enum_v<T>, R>;
|
using enable_if_enum_t = std::enable_if_t<std::is_enum_v<T>, R>;
|
||||||
|
|
||||||
template <typename T = void>
|
constexpr std::string_view pretty_name(std::string_view name, bool remove_template_suffix = true) noexcept {
|
||||||
[[nodiscard]] constexpr std::string_view nameof(std::string_view name, bool remove_template_suffix = true) noexcept {
|
if (name.size() >= 1 && (name[0] == '"' || name[0] == '\'')) {
|
||||||
static_assert(std::is_void_v<T>, "nameof::detail::nameof requires void type.");
|
|
||||||
if (name.size() >= 1 && (name.front() == '"' || name.front() == '\'')) {
|
|
||||||
return {}; // Narrow multibyte string literal.
|
return {}; // Narrow multibyte string literal.
|
||||||
} else if (name.size() >= 2 && name[0] == 'R' && (name[1] == '"' || name[1] == '\'')) {
|
} else if (name.size() >= 2 && name[0] == 'R' && (name[1] == '"' || name[1] == '\'')) {
|
||||||
return {}; // Raw string literal.
|
return {}; // Raw string literal.
|
||||||
|
@ -140,7 +252,7 @@ template <typename T = void>
|
||||||
return {}; // UTF-16 encoded string literal.
|
return {}; // UTF-16 encoded string literal.
|
||||||
} else if (name.size() >= 3 && name[0] == 'u' && name[1] == '8' && (name[2] == '"' || name[2] == '\'')) {
|
} else if (name.size() >= 3 && name[0] == 'u' && name[1] == '8' && (name[2] == '"' || name[2] == '\'')) {
|
||||||
return {}; // UTF-8 encoded string literal.
|
return {}; // UTF-8 encoded string literal.
|
||||||
} else if (name.size() >= 1 && (name.front() >= '0' && name.front() <= '9')) {
|
} else if (name.size() >= 1 && (name[0] >= '0' && name[0] <= '9')) {
|
||||||
return {}; // Invalid name.
|
return {}; // Invalid name.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,20 +318,13 @@ template <typename T = void>
|
||||||
return {}; // Invalid name.
|
return {}; // Invalid name.
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] constexpr std::string_view nameof_raw(std::string_view name) noexcept {
|
|
||||||
static_assert(std::is_void_v<T>, "nameof::detail::nameof_raw requires void type.");
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename E, E V>
|
template <typename E, E V>
|
||||||
constexpr auto n() noexcept {
|
constexpr auto n() noexcept {
|
||||||
static_assert(std::is_enum_v<E>, "nameof::detail::nameof_enum requires enum type.");
|
static_assert(std::is_enum_v<E>, "nameof::detail::n requires enum type.");
|
||||||
#if defined(__clang__) || defined(__GNUC__) && __GNUC__ >= 9
|
#if defined(__clang__) || defined(__GNUC__) && __GNUC__ >= 9
|
||||||
constexpr auto name = nameof({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
|
constexpr auto name = pretty_name({__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2});
|
||||||
#elif defined(_MSC_VER)
|
#elif defined(_MSC_VER)
|
||||||
constexpr auto name = nameof({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
|
constexpr auto name = pretty_name({__FUNCSIG__, sizeof(__FUNCSIG__) - 17});
|
||||||
#else
|
#else
|
||||||
static_assert(nameof_enum_supported<E>::value, "nameof::nameof_enum: Unsupported compiler (https://github.com/Neargye/nameof#compiler-compatibility).");
|
static_assert(nameof_enum_supported<E>::value, "nameof::nameof_enum: Unsupported compiler (https://github.com/Neargye/nameof#compiler-compatibility).");
|
||||||
return std::string_view{}; // Unsupported compiler.
|
return std::string_view{}; // Unsupported compiler.
|
||||||
|
@ -248,23 +353,16 @@ constexpr auto n() noexcept {
|
||||||
constexpr std::string_view name{__PRETTY_FUNCTION__ + 46, sizeof(__PRETTY_FUNCTION__) - 49};
|
constexpr std::string_view name{__PRETTY_FUNCTION__ + 46, sizeof(__PRETTY_FUNCTION__) - 49};
|
||||||
#elif defined(_MSC_VER)
|
#elif defined(_MSC_VER)
|
||||||
constexpr std::string_view name{__FUNCSIG__ + 63, sizeof(__FUNCSIG__) - 81 - (__FUNCSIG__[sizeof(__FUNCSIG__) - 19] == ' ' ? 1 : 0)};
|
constexpr std::string_view name{__FUNCSIG__ + 63, sizeof(__FUNCSIG__) - 81 - (__FUNCSIG__[sizeof(__FUNCSIG__) - 19] == ' ' ? 1 : 0)};
|
||||||
#else
|
|
||||||
static_assert(nameof_type_supported<T...>::value, "nameof::nameof_type: Unsupported compiler (https://github.com/Neargye/nameof#compiler-compatibility).");
|
|
||||||
return std::string_view{}; // Unsupported compiler.
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__clang__) || defined(__GNUC__) || defined(_MSC_VER)
|
if constexpr (nameof_type_supported<T...>::value) {
|
||||||
return static_string<name.size()>{name};
|
return static_string<name.size()>{name};
|
||||||
#endif
|
} else {
|
||||||
|
static_assert(nameof_type_supported<T...>::value, "nameof::nameof_type: Unsupported compiler (https://github.com/Neargye/nameof#compiler-compatibility).");
|
||||||
|
return std::string_view{}; // Unsupported compiler.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
template <typename U, typename T = identity<U>>
|
|
||||||
#else
|
|
||||||
template <typename T>
|
|
||||||
#endif
|
|
||||||
inline constexpr auto nameof_type_v = n<T>();
|
|
||||||
|
|
||||||
} // namespace nameof::detail
|
} // namespace nameof::detail
|
||||||
|
|
||||||
// Checks is nameof_type supported compiler.
|
// Checks is nameof_type supported compiler.
|
||||||
|
@ -305,26 +403,49 @@ template <auto V>
|
||||||
|
|
||||||
// Obtains string name of type, reference and cv-qualifiers are ignored.
|
// Obtains string name of type, reference and cv-qualifiers are ignored.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
[[nodiscard]] constexpr std::string_view nameof_type() noexcept {
|
[[nodiscard]] constexpr auto nameof_type() noexcept {
|
||||||
return detail::nameof_type_v<detail::remove_cvref_t<T>>;
|
#if defined(_MSC_VER)
|
||||||
|
return detail::n<detail::identity<detail::remove_cvref_t<T>>>();
|
||||||
|
#else
|
||||||
|
return detail::n<detail::remove_cvref_t<T>>();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtains string name of full type, with reference and cv-qualifiers.
|
// Obtains string name of full type, with reference and cv-qualifiers.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
[[nodiscard]] constexpr std::string_view nameof_full_type() noexcept {
|
[[nodiscard]] constexpr auto nameof_full_type() noexcept {
|
||||||
return detail::nameof_type_v<T>;
|
#if defined(_MSC_VER)
|
||||||
|
return detail::n<detail::identity<T>>();
|
||||||
|
#else
|
||||||
|
return detail::n<T>();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace nameof
|
} // namespace nameof
|
||||||
|
|
||||||
// Obtains simple (unqualified) string name of variable, function, enum, macro.
|
// Obtains simple (unqualified) string name of variable, function, macro.
|
||||||
#define NAMEOF(...) ::nameof::detail::nameof<::std::void_t<decltype(__VA_ARGS__)>>(#__VA_ARGS__, true)
|
#define NAMEOF(...) []() constexpr noexcept { \
|
||||||
|
::std::void_t<decltype(__VA_ARGS__)>(); \
|
||||||
|
constexpr auto name = ::nameof::detail::pretty_name(#__VA_ARGS__, true); \
|
||||||
|
static_assert(!name.empty(), "Expression does not have a name."); \
|
||||||
|
constexpr auto size = name.size(); \
|
||||||
|
return ::nameof::detail::static_string<size>{name}; }()
|
||||||
|
|
||||||
// Obtains simple (unqualified) full (with template suffix) string name of variable, function, enum, macro.
|
// Obtains simple (unqualified) full (with template suffix) string name of variable, function, macro.
|
||||||
#define NAMEOF_FULL(...) ::nameof::detail::nameof<::std::void_t<decltype(__VA_ARGS__)>>(#__VA_ARGS__, false)
|
#define NAMEOF_FULL(...) []() constexpr noexcept { \
|
||||||
|
::std::void_t<decltype(__VA_ARGS__)>(); \
|
||||||
|
constexpr auto name = ::nameof::detail::pretty_name(#__VA_ARGS__, false); \
|
||||||
|
static_assert(!name.empty(), "Expression does not have a name."); \
|
||||||
|
constexpr auto size = name.size(); \
|
||||||
|
return ::nameof::detail::static_string<size>{name}; }()
|
||||||
|
|
||||||
// Obtains raw string name of variable, function, enum, macro.
|
// Obtains raw string name of variable, function, macro.
|
||||||
#define NAMEOF_RAW(...) ::nameof::detail::nameof_raw<::std::void_t<decltype(__VA_ARGS__)>>(#__VA_ARGS__)
|
#define NAMEOF_RAW(...) []() constexpr noexcept { \
|
||||||
|
::std::void_t<decltype(__VA_ARGS__)>(); \
|
||||||
|
constexpr auto name = ::std::string_view{#__VA_ARGS__}; \
|
||||||
|
static_assert(!name.empty(), "Expression does not have a name."); \
|
||||||
|
constexpr auto size = name.size(); \
|
||||||
|
return ::nameof::detail::static_string<size>{name}; }()
|
||||||
|
|
||||||
// Obtains simple (unqualified) string enum name of enum variable.
|
// Obtains simple (unqualified) string enum name of enum variable.
|
||||||
#define NAMEOF_ENUM(...) ::nameof::nameof_enum(__VA_ARGS__)
|
#define NAMEOF_ENUM(...) ::nameof::nameof_enum(__VA_ARGS__)
|
||||||
|
|
Loading…
Reference in a new issue