Added more stuff like Logging, DynamicPointer, utilities for using STL allocators and NotNullable.
This commit is contained in:
parent
17bd408d3c
commit
061c58ef41
1
SModule
1
SModule
@ -10,6 +10,7 @@ mijin_sources = Split("""
|
||||
source/mijin/debug/symbol_info.cpp
|
||||
source/mijin/io/process.cpp
|
||||
source/mijin/io/stream.cpp
|
||||
source/mijin/logging/logger.cpp
|
||||
source/mijin/net/http.cpp
|
||||
source/mijin/net/ip.cpp
|
||||
source/mijin/net/socket.cpp
|
||||
|
@ -2,3 +2,4 @@
|
||||
#pragma once
|
||||
|
||||
#include "./exception.hpp"
|
||||
#include "./version_support.hpp"
|
||||
|
@ -23,10 +23,12 @@
|
||||
#else
|
||||
#if defined(MIJIN_TEST_NO_NOEXCEPT) // only use for testing
|
||||
#define MIJIN_NOEXCEPT
|
||||
#define MIJIN_NOEXCEPT_IF(x)
|
||||
#define MIJIN_THROWS
|
||||
#define MIJIN_CONDITIONAL_NOEXCEPT(...)
|
||||
#else
|
||||
#define MIJIN_NOEXCEPT noexcept
|
||||
#define MIJIN_NOEXCEPT_IF(x) noexcept(x)
|
||||
#define MIJIN_THROWS noexcept
|
||||
#define MIJIN_CONDITIONAL_NOEXCEPT(...) noexcept(__VA_ARGS__)
|
||||
#endif
|
||||
|
13
source/mijin/internal/version_support.hpp
Normal file
13
source/mijin/internal/version_support.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)
|
||||
#define MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED 1
|
||||
|
||||
#if defined(__cpp_deleted_function)
|
||||
#define MIJIN_DELETE(reason) = delete(reason)
|
||||
#else
|
||||
#define MIJIN_DELETE(reason) = delete
|
||||
#endif
|
||||
|
||||
#endif // !defined(MIJIN_INTERNAL_VERSION_SUPPORT_HPP_INCLUDED)
|
176
source/mijin/logging/formatting.hpp
Normal file
176
source/mijin/logging/formatting.hpp
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_FORMATTING_HPP_INCLUDED 1
|
||||
|
||||
#include <flat_map>
|
||||
#include <format>
|
||||
#include <variant>
|
||||
#include "./logger.hpp"
|
||||
#include "../memory/dynamic_pointer.hpp"
|
||||
#include "../memory/memutil.hpp"
|
||||
#include "../memory/virtual_allocator.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<allocator_type_for<char> TAllocator = std::allocator<char>>
|
||||
class LogFormatter
|
||||
{
|
||||
public:
|
||||
using string_t = std::basic_string<char, std::char_traits<char>, TAllocator>;
|
||||
|
||||
virtual ~LogFormatter() noexcept = default;
|
||||
|
||||
virtual void format(const LogMessage& message, string_t& outFormatted) noexcept = 0;
|
||||
};
|
||||
|
||||
template<allocator_type_for<char> TAllocator = std::allocator<char>>
|
||||
class SimpleLogFormatter : public LogFormatter<TAllocator>
|
||||
{
|
||||
public:
|
||||
using typename LogFormatter<TAllocator>::string_t;
|
||||
private:
|
||||
string_t mFormat;
|
||||
public:
|
||||
explicit SimpleLogFormatter(string_t format) MIJIN_NOEXCEPT : mFormat(std::move(format)) {}
|
||||
|
||||
void format(const LogMessage& message, string_t& outFormatted) noexcept override;
|
||||
};
|
||||
|
||||
template<template<typename> typename TAllocator = std::allocator,
|
||||
deleter_type<LogFormatter<TAllocator<char>>> TDeleter = AllocatorDeleter<TAllocator<LogFormatter<TAllocator<char>>>>>
|
||||
requires(allocator_type<TAllocator<char>>)
|
||||
class FormattingLogSink : public LogSink
|
||||
{
|
||||
public:
|
||||
using allocator_t = TAllocator<char>;
|
||||
using formatter_t = LogFormatter<allocator_t>;
|
||||
using formatter_deleter_t = TDeleter;
|
||||
using formatter_ptr_t = DynamicPointer<formatter_t, formatter_deleter_t>;
|
||||
using string_t = formatter_t::string_t;
|
||||
private:
|
||||
not_null_t<formatter_ptr_t> mFormatter;
|
||||
string_t mBuffer;
|
||||
public:
|
||||
explicit FormattingLogSink(not_null_t<formatter_ptr_t> formatter, TAllocator<char> allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<char>>)
|
||||
: mFormatter(std::move(formatter)), mBuffer(std::move(allocator))
|
||||
{}
|
||||
|
||||
virtual void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT = 0;
|
||||
|
||||
void handleMessage(const LogMessage& message) noexcept override
|
||||
{
|
||||
mBuffer.clear();
|
||||
mFormatter->format(message, mBuffer);
|
||||
handleMessageFormatted(message, mBuffer.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
template<allocator_type_for<char> TAllocator>
|
||||
void SimpleLogFormatter<TAllocator>::format(const LogMessage& message, string_t& outFormatted) noexcept
|
||||
{
|
||||
for (auto pos = mFormat.begin(); pos != mFormat.end(); ++pos)
|
||||
{
|
||||
if (*pos == '{')
|
||||
{
|
||||
++pos;
|
||||
if (*pos == '{')
|
||||
{
|
||||
// double {
|
||||
outFormatted += '{';
|
||||
continue;
|
||||
}
|
||||
const auto argStart = pos;
|
||||
static const std::string_view endChars = ":}";
|
||||
pos = std::find_first_of(pos, mFormat.end(), endChars.begin(), endChars.end());
|
||||
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
|
||||
|
||||
const std::string_view argName(argStart, pos);
|
||||
std::string argFormat;
|
||||
if (*pos == ':')
|
||||
{
|
||||
const auto formatStart = pos;
|
||||
pos = std::find(pos, mFormat.end(), '}');
|
||||
MIJIN_ASSERT(pos != mFormat.end(), "Invalid format.");
|
||||
argFormat = std::string_view(formatStart, pos);
|
||||
}
|
||||
|
||||
// small utility that uses the provided string buffer for storing the format string
|
||||
auto formatInline = [&](const auto& value)
|
||||
{
|
||||
using type_t = std::decay_t<decltype(value)>;
|
||||
|
||||
// if there is no format, just directly print the value
|
||||
if (argFormat.empty())
|
||||
{
|
||||
if constexpr (std::is_arithmetic_v<type_t>)
|
||||
{
|
||||
std::format_to(std::back_inserter(outFormatted), "{}", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
outFormatted += value;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// first copy the format string + braces into the buffer
|
||||
const auto formatStart = outFormatted.size();
|
||||
outFormatted += '{';
|
||||
outFormatted += argFormat;
|
||||
outFormatted += '}';
|
||||
const auto formatEnd = outFormatted.size();
|
||||
auto format = std::string_view(outFormatted).substr(formatStart, formatEnd - formatStart);
|
||||
|
||||
// then append the formatted text
|
||||
std::vformat_to(std::back_inserter(outFormatted), format, std::make_format_args(value));
|
||||
|
||||
// and then remove the format from the buffer again
|
||||
outFormatted.erase(formatStart, formatEnd - formatStart);
|
||||
};
|
||||
|
||||
if (argName == "text")
|
||||
{
|
||||
formatInline(message.text);
|
||||
}
|
||||
else if (argName == "channel")
|
||||
{
|
||||
formatInline(message.channel->name);
|
||||
}
|
||||
else if (argName == "file")
|
||||
{
|
||||
formatInline(message.sourceLocation.file_name());
|
||||
}
|
||||
else if (argName == "function")
|
||||
{
|
||||
formatInline(message.sourceLocation.function_name());
|
||||
}
|
||||
else if (argName == "line")
|
||||
{
|
||||
formatInline(message.sourceLocation.line());
|
||||
}
|
||||
else if (argName == "column")
|
||||
{
|
||||
formatInline(message.sourceLocation.column());
|
||||
}
|
||||
else if (argName == "level")
|
||||
{
|
||||
formatInline(message.level->name);
|
||||
}
|
||||
else
|
||||
{
|
||||
MIJIN_ERROR("Invalid format argument name.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outFormatted += *pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_FORMATTING_HPP_INCLUDED)
|
13
source/mijin/logging/logger.cpp
Normal file
13
source/mijin/logging/logger.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
#include "logger.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
|
||||
//
|
||||
// public constants
|
||||
//
|
||||
|
||||
MIJIN_DEFINE_LOG_CHANNEL(GENERAL)
|
||||
|
||||
} // namespace mijin
|
173
source/mijin/logging/logger.hpp
Normal file
173
source/mijin/logging/logger.hpp
Normal file
@ -0,0 +1,173 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_LOGGER_HPP_INCLUDED 1
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <source_location>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
struct LogLevel
|
||||
{
|
||||
const char* name;
|
||||
int value;
|
||||
|
||||
explicit operator int() const MIJIN_NOEXCEPT { return value; }
|
||||
|
||||
auto operator<=>(const LogLevel& other) const MIJIN_NOEXCEPT { return value <=> other.value; }
|
||||
};
|
||||
|
||||
struct LogChannel
|
||||
{
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct LogMessage
|
||||
{
|
||||
const char* text;
|
||||
const LogChannel* channel;
|
||||
const LogLevel* level;
|
||||
std::source_location sourceLocation;
|
||||
};
|
||||
|
||||
class LogSink
|
||||
{
|
||||
public:
|
||||
virtual ~LogSink() noexcept = default;
|
||||
|
||||
virtual void handleMessage(const LogMessage& message) MIJIN_NOEXCEPT = 0;
|
||||
};
|
||||
|
||||
template<template<typename T> typename TAllocator = std::allocator>
|
||||
class Logger
|
||||
{
|
||||
private:
|
||||
std::vector<LogSink*, TAllocator<LogSink*>> mSinks;
|
||||
public:
|
||||
explicit Logger(TAllocator<LogSink*> allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<LogSink*>>)
|
||||
: mSinks(std::move(allocator))
|
||||
{}
|
||||
|
||||
Logger(const Logger&) = default;
|
||||
|
||||
Logger(Logger&&) = default;
|
||||
|
||||
Logger& operator=(const Logger&) = default;
|
||||
|
||||
Logger& operator=(Logger&&) = default;
|
||||
|
||||
void addSink(LogSink& sink)
|
||||
{
|
||||
mSinks.push_back(&sink);
|
||||
}
|
||||
|
||||
void postMessage(const LogMessage& message) const MIJIN_NOEXCEPT
|
||||
{
|
||||
for (LogSink* sink: mSinks)
|
||||
{
|
||||
sink->handleMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation, const char* msg) const MIJIN_NOEXCEPT
|
||||
{
|
||||
postMessage({
|
||||
.text = msg,
|
||||
.channel = &channel,
|
||||
.level = &level,
|
||||
.sourceLocation = std::move(sourceLocation)
|
||||
});
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
void log(const LogLevel& level, const LogChannel& channel, std::source_location sourceLocation,
|
||||
std::format_string<TArgs...> fmt, TArgs&& ... args) const
|
||||
MIJIN_NOEXCEPT_IF(noexcept(std::declval<TAllocator<char>>().allocate(1)))
|
||||
{
|
||||
std::basic_string<char, std::char_traits<char>, TAllocator<char>> buffer(TAllocator<char>(mSinks.get_allocator()));
|
||||
std::format_to(std::back_inserter(buffer), fmt, std::forward<TArgs>(args)...);
|
||||
log(level, channel, std::move(sourceLocation), buffer.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
#define MIJIN_DECLARE_LOG_CHANNEL(cnlName) \
|
||||
namespace mijin_log_channel \
|
||||
{ \
|
||||
extern const ::mijin::LogChannel cnlName; \
|
||||
}
|
||||
|
||||
#define MIJIN_DEFINE_LOG_CHANNEL(cnlName) \
|
||||
namespace mijin_log_channel \
|
||||
{ \
|
||||
const ::mijin::LogChannel cnlName { \
|
||||
.name = #cnlName \
|
||||
}; \
|
||||
}
|
||||
|
||||
#define MIJIN_DEFINE_LOG_LEVEL(lvlName, lvlValue) \
|
||||
namespace mijin_log_level \
|
||||
{ \
|
||||
inline constexpr ::mijin::LogLevel lvlName{ \
|
||||
.name = #lvlName, \
|
||||
.value = lvlValue \
|
||||
}; \
|
||||
}
|
||||
|
||||
MIJIN_DECLARE_LOG_CHANNEL(GENERAL)
|
||||
MIJIN_DEFINE_LOG_LEVEL(DEBUG, -1000)
|
||||
MIJIN_DEFINE_LOG_LEVEL(VERBOSE, -500)
|
||||
MIJIN_DEFINE_LOG_LEVEL(INFO, 0)
|
||||
MIJIN_DEFINE_LOG_LEVEL(WARNING, 500)
|
||||
MIJIN_DEFINE_LOG_LEVEL(ERROR, 1000)
|
||||
|
||||
#if defined(MIJIN_MIN_LOGLEVEL)
|
||||
inline constexpr int MIN_LOG_LEVEL = static_cast<int>(MIJIN_MIN_LOGLEVEL);
|
||||
#elif defined(MIJIN_DEBUG)
|
||||
inline constexpr int MIN_LOG_LEVEL = mijin_log_level::DEBUG.value;
|
||||
#else
|
||||
inline constexpr int MIN_LOG_LEVEL = mijin_log_level::VERBOSE.value;
|
||||
#endif
|
||||
|
||||
#define MIJIN_IMPORT_LOG_DEFAULTS \
|
||||
namespace mijin_log_channel \
|
||||
{ \
|
||||
using ::mijin::mijin_log_channel::GENERAL; \
|
||||
} \
|
||||
\
|
||||
namespace mijin_log_level \
|
||||
{ \
|
||||
using ::mijin::mijin_log_level::DEBUG; \
|
||||
using ::mijin::mijin_log_level::VERBOSE; \
|
||||
using ::mijin::mijin_log_level::INFO; \
|
||||
using ::mijin::mijin_log_level::WARNING; \
|
||||
using ::mijin::mijin_log_level::ERROR; \
|
||||
}
|
||||
|
||||
#define MIJIN_LOG_ALWAYS(level, channel, ...) mijin__getLogger__().log( \
|
||||
mijin_log_level::level, mijin_log_channel::channel, std::source_location::current(), __VA_ARGS__ \
|
||||
)
|
||||
#define MIJIN_LOG(level, channel, ...) \
|
||||
if constexpr (mijin_log_level::level.value < mijin::MIN_LOG_LEVEL) {} \
|
||||
else MIJIN_LOG_ALWAYS(level, channel, __VA_ARGS__)
|
||||
|
||||
#define MIJIN_SET_CLASS_LOGGER(loggerExpr) \
|
||||
const auto& mijin__getLogger__() const noexcept \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
}
|
||||
#define MIJIN_SET_SCOPE_LOGGER(loggerExpr) \
|
||||
auto mijin__getLogger__ = [&]() -> const auto& \
|
||||
{ \
|
||||
return loggerExpr; \
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_LOGGER_HPP_INCLUDED)
|
40
source/mijin/logging/stdio_sink.hpp
Normal file
40
source/mijin/logging/stdio_sink.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
||||
#define MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED 1
|
||||
|
||||
#include "./formatting.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<template<typename> typename TAllocator = std::allocator,
|
||||
deleter_type<LogFormatter<TAllocator<char>>> TDeleter = AllocatorDeleter<TAllocator<LogFormatter<TAllocator<char>>>>>
|
||||
requires(allocator_type<TAllocator<char>>)
|
||||
class StdioSink : public FormattingLogSink<TAllocator, TDeleter>
|
||||
{
|
||||
public:
|
||||
using base_t = FormattingLogSink<TAllocator, TDeleter>;
|
||||
using typename base_t::formatter_ptr_t;
|
||||
|
||||
explicit StdioSink(not_null_t<formatter_ptr_t> formatter, TAllocator<char> allocator = {})
|
||||
MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator<char>>)
|
||||
: base_t(std::move(formatter), std::move(allocator)) {}
|
||||
|
||||
void handleMessageFormatted(const LogMessage& message, const char* formatted) MIJIN_NOEXCEPT override
|
||||
{
|
||||
if (*message.level >= mijin_log_level::WARNING)
|
||||
{
|
||||
std::fputs(formatted, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::puts(formatted);
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
|
||||
#endif // !defined(MIJIN_LOGGING_STDIO_SINK_HPP_INCLUDED)
|
176
source/mijin/memory/dynamic_pointer.hpp
Normal file
176
source/mijin/memory/dynamic_pointer.hpp
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED 1
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
#include "../memory/memutil.hpp"
|
||||
#include "../util/concepts.hpp"
|
||||
#include "../util/flag.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
MIJIN_DEFINE_FLAG(Owning);
|
||||
|
||||
template<typename T, deleter_type<T> TDeleter = std::default_delete<T>>
|
||||
class DynamicPointer
|
||||
{
|
||||
public:
|
||||
using pointer = T*;
|
||||
using element_type = T;
|
||||
using deleter_t = TDeleter;
|
||||
private:
|
||||
std::uintptr_t mData = 0;
|
||||
[[no_unique_address]] TDeleter mDeleter;
|
||||
public:
|
||||
constexpr DynamicPointer(std::nullptr_t = nullptr) MIJIN_NOEXCEPT {}
|
||||
DynamicPointer(const DynamicPointer&) = delete;
|
||||
constexpr DynamicPointer(pointer ptr, Owning owning, TDeleter deleter = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TDeleter>)
|
||||
: mData(std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0)), mDeleter(std::move(deleter))
|
||||
{
|
||||
MIJIN_ASSERT((std::bit_cast<std::uintptr_t>(ptr) & 1) == 0, "Invalid address, DynamicPointer requires addresses to be divisible by two.");
|
||||
}
|
||||
template<typename TOther, typename TOtherDeleter> requires (std::is_constructible_v<TDeleter, TOtherDeleter&&>)
|
||||
constexpr DynamicPointer(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_convertible_v<TOtherDeleter, TDeleter>))
|
||||
: mData(std::exchange(other.mData, 0)), mDeleter(std::move(other.mDeleter)) {
|
||||
MIJIN_ASSERT(other.mData == 0, "");
|
||||
}
|
||||
constexpr ~DynamicPointer() noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
DynamicPointer& operator=(const DynamicPointer&) = delete;
|
||||
DynamicPointer& operator=(std::nullptr_t) MIJIN_NOEXCEPT
|
||||
{
|
||||
reset();
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename TOther, typename TOtherDeleter> requires(std::is_assignable_v<TDeleter, TOtherDeleter>)
|
||||
DynamicPointer& operator=(DynamicPointer<TOther, TOtherDeleter>&& other) MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TDeleter, TOtherDeleter>))
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
reset();
|
||||
mData = std::exchange(other.mData, 0);
|
||||
mDeleter = std::move(other.mDeleter);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename TOther, typename TOtherDeleter> requires(std::equality_comparable_with<T, TOther>)
|
||||
auto operator<=>(const DynamicPointer<TOther, TOtherDeleter>& other) MIJIN_NOEXCEPT
|
||||
{
|
||||
return mData <=> other.mData;
|
||||
}
|
||||
|
||||
constexpr bool operator==(std::nullptr_t) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return empty();
|
||||
}
|
||||
|
||||
constexpr bool operator!=(std::nullptr_t) const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !empty();
|
||||
}
|
||||
|
||||
constexpr operator bool() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return !empty();
|
||||
}
|
||||
|
||||
constexpr bool operator!() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return empty();
|
||||
}
|
||||
|
||||
constexpr pointer operator->() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return get();
|
||||
}
|
||||
|
||||
constexpr element_type& operator*() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return *get();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool isOwning() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return (mData & 1) == 1;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool empty() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return mData == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr pointer get() const MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::bit_cast<pointer>(mData & ~1);
|
||||
}
|
||||
|
||||
constexpr void reset(pointer ptr, Owning owning) MIJIN_NOEXCEPT
|
||||
{
|
||||
if (isOwning())
|
||||
{
|
||||
mDeleter(get());
|
||||
}
|
||||
mData = std::bit_cast<std::uintptr_t>(ptr) | (owning ? 1 : 0);
|
||||
}
|
||||
|
||||
constexpr void reset() MIJIN_NOEXCEPT
|
||||
{
|
||||
reset(nullptr, Owning::NO);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
pointer release() MIJIN_NOEXCEPT
|
||||
{
|
||||
return std::bit_cast<pointer>(std::exchange(mData, 0) & ~1);
|
||||
}
|
||||
|
||||
template<typename TOther, deleter_type<TOther> TOtherDeleter>
|
||||
friend class DynamicPointer;
|
||||
};
|
||||
|
||||
template<typename T, typename TDeleter>
|
||||
bool operator==(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
|
||||
{
|
||||
return pointer == nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename TDeleter>
|
||||
bool operator!=(std::nullptr_t, const DynamicPointer<T, TDeleter>& pointer) MIJIN_NOEXCEPT
|
||||
{
|
||||
return pointer != nullptr;
|
||||
}
|
||||
|
||||
template<typename T, typename... TArgs>
|
||||
DynamicPointer<T, std::default_delete<T>> makeDynamic(TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...>))
|
||||
{
|
||||
return DynamicPointer<T, std::default_delete<T>>(new T(std::forward<TArgs>(args)...), Owning::YES);
|
||||
}
|
||||
|
||||
template<typename T, allocator_type_for<T> TAllocator, typename... TArgs>
|
||||
DynamicPointer<T, AllocatorDeleter<TAllocator>> makeDynamicWithAllocator(TAllocator allocator, TArgs&&... args)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArgs...> && std::is_nothrow_move_constructible_v<TAllocator>))
|
||||
{
|
||||
T* obj = allocator.allocate(1);
|
||||
if (obj != nullptr)
|
||||
{
|
||||
::new(obj) T(std::forward<TArgs>(args)...);
|
||||
}
|
||||
return DynamicPointer<T, AllocatorDeleter<TAllocator>>(obj, Owning::YES, AllocatorDeleter<TAllocator>(std::move(allocator)));
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_DYNAMIC_POINTER_HPP_INCLUDED)
|
85
source/mijin/memory/memutil.hpp
Normal file
85
source/mijin/memory/memutil.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include "../internal/common.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename TAllocator>
|
||||
class AllocatorDeleter
|
||||
{
|
||||
public:
|
||||
using value_type = std::allocator_traits<TAllocator>::value_type;
|
||||
using pointer = std::allocator_traits<TAllocator>::pointer;
|
||||
|
||||
private:
|
||||
[[no_unique_address]] TAllocator allocator_;
|
||||
|
||||
public:
|
||||
explicit AllocatorDeleter(TAllocator allocator = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TAllocator>)
|
||||
: allocator_(std::move(allocator)) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, const TOtherAllocator&>)
|
||||
AllocatorDeleter(const AllocatorDeleter<TOtherAllocator>& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator>))
|
||||
: allocator_(other.allocator_) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_constructible_v<TAllocator, TOtherAllocator&&>)
|
||||
AllocatorDeleter(AllocatorDeleter<TOtherAllocator>&& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<TAllocator, TOtherAllocator&&>))
|
||||
: allocator_(std::move(other.allocator_)) {}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, const TOtherAllocator&>)
|
||||
AllocatorDeleter& operator=(const AllocatorDeleter<TOtherAllocator>& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, const TOtherAllocator&>))
|
||||
{
|
||||
if (this != static_cast<const void*>(&other))
|
||||
{
|
||||
allocator_ = other.allocator_;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename TOtherAllocator> requires (std::is_assignable_v<TAllocator&, TOtherAllocator&&>)
|
||||
AllocatorDeleter& operator=(AllocatorDeleter<TOtherAllocator>&& other)
|
||||
MIJIN_NOEXCEPT_IF((std::is_nothrow_assignable_v<TAllocator&, TOtherAllocator&&>))
|
||||
{
|
||||
if (this != static_cast<const void*>(&other))
|
||||
{
|
||||
allocator_ = std::move(other.allocator_);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(pointer ptr) MIJIN_NOEXCEPT_IF(noexcept(allocator_.deallocate(ptr, sizeof(value_type))))
|
||||
{
|
||||
allocator_.deallocate(ptr, sizeof(value_type));
|
||||
}
|
||||
|
||||
template<typename TOtherAllocator>
|
||||
friend class AllocatorDeleter;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class AllocatorDeleter<std::allocator<T>>
|
||||
{
|
||||
public:
|
||||
AllocatorDeleter() noexcept = default;
|
||||
template<typename TOther>
|
||||
AllocatorDeleter(const AllocatorDeleter<std::allocator<TOther>>&) noexcept {}
|
||||
|
||||
template<typename TOther>
|
||||
AllocatorDeleter& operator=(const AllocatorDeleter<std::allocator<TOther>>&) noexcept { return *this; }
|
||||
|
||||
void operator()(T* ptr) const MIJIN_NOEXCEPT
|
||||
{
|
||||
delete ptr;
|
||||
}
|
||||
};
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_MEMUTIL_HPP_INCLUDED)
|
56
source/mijin/memory/virtual_allocator.hpp
Normal file
56
source/mijin/memory/virtual_allocator.hpp
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)
|
||||
#define MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED 1
|
||||
|
||||
#include "../internal/common.hpp"
|
||||
#include "../util/annot.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T>
|
||||
class VirtualAllocator
|
||||
{
|
||||
public:
|
||||
virtual ~VirtualAllocator() noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
virtual owner_t<T*> allocate(std::size_t count) noexcept;
|
||||
virtual void deallocate(owner_t<T*> ptr, std::size_t count) noexcept;
|
||||
};
|
||||
|
||||
template<typename T, typename TImpl>
|
||||
class WrappedVirtualAllocator : public VirtualAllocator<T>
|
||||
{
|
||||
private:
|
||||
[[no_unique_address]] TImpl mImpl;
|
||||
public:
|
||||
explicit constexpr WrappedVirtualAllocator(TImpl impl = {}) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<TImpl>)
|
||||
: mImpl(std::move(impl)) {}
|
||||
constexpr WrappedVirtualAllocator(const WrappedVirtualAllocator&) = default;
|
||||
constexpr WrappedVirtualAllocator(WrappedVirtualAllocator&&) = default;
|
||||
|
||||
WrappedVirtualAllocator& operator=(const WrappedVirtualAllocator&) = default;
|
||||
WrappedVirtualAllocator& operator=(WrappedVirtualAllocator&&) = default;
|
||||
|
||||
[[nodiscard]]
|
||||
owner_t<T*> allocate(std::size_t count) noexcept override
|
||||
{
|
||||
return mImpl.allocate(count);
|
||||
}
|
||||
|
||||
void deallocate(owner_t<T*> ptr, std::size_t count) noexcept override
|
||||
{
|
||||
mImpl.deallocate(ptr, count);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
WrappedVirtualAllocator<typename T::value_type, T> makeVirtualAllocator(T allocator) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
{
|
||||
return WrappedVirtualAllocator<typename T::value_type, T>(std::move(allocator));
|
||||
}
|
||||
} // namespace mijin
|
||||
|
||||
#endif // !defined(MIJIN_MEMORY_VIRTUAL_ALLOCATOR_HPP_INCLUDED)
|
146
source/mijin/util/annot.hpp
Normal file
146
source/mijin/util/annot.hpp
Normal file
@ -0,0 +1,146 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_ANNOT_HPP_INCLUDED 1
|
||||
|
||||
#include <utility>
|
||||
#include "../internal/common.hpp"
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if !defined(__has_include)
|
||||
#define __has_include(x) (false)
|
||||
#endif
|
||||
|
||||
#if !defined(MIJIN_USE_GSL)
|
||||
#if __has_include(<gsl/gsl>)
|
||||
#define MIJIN_USE_GSL 1
|
||||
#else
|
||||
#define MIJIN_USE_GSL 0
|
||||
#endif
|
||||
#endif // !defined(MIJIN_USE_GSL)
|
||||
|
||||
#include <concepts>
|
||||
#include "./concepts.hpp"
|
||||
|
||||
#if MIJIN_USE_GSL
|
||||
#include <gsl/gsl>
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
template<typename T> requires(!std::is_same_v<T, std::nullptr_t>) && requires(T t) { t == nullptr; }
|
||||
class NotNullable
|
||||
{
|
||||
private:
|
||||
T base_;
|
||||
public:
|
||||
template<typename U> requires(std::is_same_v<T, U> && std::is_copy_constructible_v<T>)
|
||||
constexpr NotNullable(U base) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
: base_(base)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
template<typename TArg, typename... TArgs> requires(!std::is_same_v<TArg, std::nullptr_t>
|
||||
&& (!std::is_same_v<TArg, T> && sizeof...(TArgs) == 0)
|
||||
&& std::is_constructible_v<T, TArg&&, TArgs&&...>)
|
||||
constexpr NotNullable(TArg&& arg, TArgs&&... args) MIJIN_NOEXCEPT_IF((std::is_nothrow_constructible_v<T, TArg&&, TArgs&&...>))
|
||||
: base_(std::forward<TArg>(arg), std::forward<TArgs>(args)...)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(T&& base) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
requires(std::is_move_constructible_v<T>)
|
||||
: base_(std::move(base))
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_constructible_v<T>)
|
||||
requires(std::is_move_constructible_v<T>)
|
||||
: base_(std::exchange(other.base_, nullptr))
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Constructed non-nullable type with nullptr.");
|
||||
}
|
||||
constexpr NotNullable(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
constexpr NotNullable& operator=(const NotNullable& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_copy_constructible_v<T>)
|
||||
requires(std::is_copy_constructible_v<T>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->base_ = other.base_;
|
||||
}
|
||||
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr NotNullable& operator=(NotNullable&& other) MIJIN_NOEXCEPT_IF(std::is_nothrow_move_assignable_v<T>)
|
||||
requires(std::is_move_assignable_v<T>)
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->base_ = std::exchange(other.base_, nullptr);
|
||||
}
|
||||
MIJIN_ASSERT(base_ != nullptr, "Assigned nullptr to non-nullable type."); // might still happen if the other type was moved from
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr NotNullable& operator=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
template<std::equality_comparable_with<T> TOther>
|
||||
bool operator==(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
|
||||
{
|
||||
return base_ == other.base_;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther>
|
||||
bool operator!=(const NotNullable<TOther>& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
|
||||
{
|
||||
return base_ != other.base_;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther> requires(!std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator==(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() == std::declval<TOther>()))
|
||||
{
|
||||
return base_ == other;
|
||||
}
|
||||
|
||||
template<std::equality_comparable_with<T> TOther> requires(!std::is_same_v<TOther, std::nullptr_t>)
|
||||
bool operator!=(const TOther& other) MIJIN_NOEXCEPT_IF(noexcept(std::declval<T>() != std::declval<TOther>()))
|
||||
{
|
||||
return base_ != other;
|
||||
}
|
||||
|
||||
bool operator==(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
bool operator!=(std::nullptr_t) MIJIN_DELETE("Type is not nullable.");
|
||||
|
||||
constexpr operator const T&() const MIJIN_NOEXCEPT { return get(); }
|
||||
constexpr operator std::nullptr_t() const MIJIN_DELETE("Type is not nullable.");
|
||||
constexpr const T& operator->() const MIJIN_NOEXCEPT { return get(); }
|
||||
constexpr decltype(auto) operator*() const MIJIN_NOEXCEPT_IF(noexcept(*get())) { return *get(); }
|
||||
|
||||
NotNullable& operator++() MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator--() MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable operator++(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable operator--(int) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator+=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
NotNullable& operator-=(std::ptrdiff_t) MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
void operator[](std::ptrdiff_t) const MIJIN_DELETE("Operator disabled for non-nullable types.");
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr const T& get() const MIJIN_NOEXCEPT { return base_; }
|
||||
};
|
||||
|
||||
#if MIJIN_USE_GSL
|
||||
template<mijin::pointer_type T>
|
||||
using owner_t = gsl::owner<T>;
|
||||
#else
|
||||
template<mijin::pointer_type T>
|
||||
using owner_t = T;
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
using not_null_t = NotNullable<T>;
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_UTIL_ANNOT_HPP_INCLUDED)
|
@ -5,6 +5,7 @@
|
||||
#define MIJIN_UTIL_CONCEPTS_HPP_INCLUDED 1
|
||||
|
||||
#include <type_traits>
|
||||
#include "./traits.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
@ -39,6 +40,32 @@ concept pointer_type = std::is_pointer_v<T>;
|
||||
template<typename T>
|
||||
concept reference_type = std::is_reference_v<T>;
|
||||
|
||||
namespace impl
|
||||
{
|
||||
template<typename T>
|
||||
using pointer_t = typename T::pointer;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
concept allocator_type = requires(T alloc, typename T::value_type value, detect_or_t<typename T::value_type*, impl::pointer_t, T> pointer, int count)
|
||||
{
|
||||
typename T::value_type;
|
||||
{ alloc.allocate(count) } -> std::same_as<decltype(pointer)>;
|
||||
{ alloc.deallocate(pointer, count) } -> std::same_as<void>;
|
||||
} && !std::is_const_v<typename T::value_type> && !std::is_volatile_v<typename T::value_type>;
|
||||
|
||||
template<typename T, typename TOther>
|
||||
concept allocator_type_for = allocator_type<T> && std::is_same_v<typename T::value_type, TOther>;
|
||||
|
||||
template<template<typename> typename T>
|
||||
concept allocator_tmpl = allocator_type<T<int>>;
|
||||
|
||||
template<typename T, typename TData>
|
||||
concept deleter_type = requires(T deleter, TData* ptr)
|
||||
{
|
||||
deleter(ptr);
|
||||
};
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
@ -5,11 +5,14 @@
|
||||
#include "../debug/assert.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#include <bit>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#elif MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
#include <array>
|
||||
#include <malloc.h>
|
||||
#include <windows.h>
|
||||
#include "../util/winundef.hpp"
|
||||
#endif
|
||||
@ -139,4 +142,40 @@ std::string getExecutablePath() MIJIN_NOEXCEPT
|
||||
#endif
|
||||
}
|
||||
|
||||
void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
return _aligned_alloc(size, alignment);
|
||||
#else
|
||||
return std::aligned_alloc(alignment, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
return _aligned_realloc(ptr, size, alignment);
|
||||
#else
|
||||
void* newPtr = std::realloc(ptr, size);
|
||||
if (newPtr == ptr || (std::bit_cast<std::uintptr_t>(newPtr) % alignment) == 0)
|
||||
{
|
||||
return newPtr;
|
||||
}
|
||||
// bad luck, have to copy a second time
|
||||
void* newPtr2 = std::aligned_alloc(alignment, size);
|
||||
std::memcpy(newPtr2, newPtr, size);
|
||||
std::free(newPtr);
|
||||
return newPtr2;
|
||||
#endif
|
||||
}
|
||||
|
||||
void alignedFree(void* ptr)
|
||||
{
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_WINDOWS
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
std::free(ptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mijin
|
||||
|
@ -81,6 +81,10 @@ void setCurrentThreadName(const char* threadName) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]] std::string makeLibraryFilename(std::string_view libraryName) MIJIN_NOEXCEPT;
|
||||
|
||||
[[nodiscard]] void* alignedAlloc(std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
|
||||
[[nodiscard]] void* alignedRealloc(void* ptr, std::size_t alignment, std::size_t size) MIJIN_NOEXCEPT;
|
||||
void alignedFree(void* ptr);
|
||||
|
||||
SharedLibrary::~SharedLibrary() MIJIN_NOEXCEPT
|
||||
{
|
||||
close();
|
||||
|
@ -148,6 +148,23 @@ struct is_template_instance<TTemplate, TTemplate<TArgs...>> : std::true_type {};
|
||||
template<template<typename...> typename TTemplate, typename TType>
|
||||
constexpr bool is_template_instance_v = is_template_instance<TTemplate, TType>::value;
|
||||
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
struct detect_or
|
||||
{
|
||||
using type = TDefault;
|
||||
static constexpr bool detected = false;
|
||||
};
|
||||
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
requires requires { typename TOper<TArgs...>; }
|
||||
struct detect_or<TDefault, TOper, TArgs...>
|
||||
{
|
||||
using type = TOper<TArgs...>;
|
||||
static constexpr bool detected = true;
|
||||
};
|
||||
template<typename TDefault, template<typename...> typename TOper, typename... TArgs>
|
||||
using detect_or_t = detect_or<TDefault, TOper, TArgs...>::type;
|
||||
|
||||
//
|
||||
// public functions
|
||||
//
|
||||
|
Loading…
x
Reference in New Issue
Block a user