remove all uses of std::string in filament

- libbackend (except webgpu)
- libfilament
- libutils

std::string generates a lot of code bloat, we use CString instead.

We also update CString to be more compatible with std::string's api.
This commit is contained in:
Mathias Agopian
2025-08-28 17:05:26 -07:00
committed by Mathias Agopian
parent 1c2fe0ce39
commit d3ff60af21
17 changed files with 725 additions and 126 deletions

View File

@@ -57,7 +57,7 @@ static void printParameterPack(io::ostream& out, const FIRST& first, const REMAI
printParameterPack(out, rest...);
}
static UTILS_NOINLINE UTILS_UNUSED std::string extractMethodName(std::string& command) noexcept {
static UTILS_NOINLINE UTILS_UNUSED std::string_view extractMethodName(std::string_view command) noexcept {
constexpr const char startPattern[] = "::Command<&filament::backend::Driver::";
auto pos = command.rfind(startPattern);
auto end = command.rfind('(');
@@ -133,9 +133,9 @@ template<std::size_t... I>
void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log(std::index_sequence<I...>) noexcept {
#if DEBUG_COMMAND_STREAM
static_assert(UTILS_HAS_RTTI, "DEBUG_COMMAND_STREAM can only be used with RTTI");
std::string command = utils::CallStack::demangleTypeName(typeid(Command).name()).c_str();
DLOG(INFO) << extractMethodName(command) << " : size=" << sizeof(Command);
utils::io::sstream parameterPack;
CString command = CallStack::demangleTypeName(typeid(Command).name());
DLOG(INFO) << extractMethodName({command.data(), command.size()}) << " : size=" << sizeof(Command);
io::sstream parameterPack;
printParameterPack(parameterPack, std::get<I>(mArgs)...);
DLOG(INFO) << "\t" << parameterPack.c_str();
#endif
@@ -164,7 +164,7 @@ void CommandType<void (Driver::*)(ARGS...)>::Command<METHOD>::log() noexcept {
// ------------------------------------------------------------------------------------------------
void CustomCommand::execute(Driver&, CommandBase* base, intptr_t* next) {
*next = CustomCommand::align(sizeof(CustomCommand));
*next = align(sizeof(CustomCommand));
static_cast<CustomCommand*>(base)->mCommand();
static_cast<CustomCommand*>(base)->~CustomCommand();
}

View File

@@ -21,7 +21,8 @@
#include <utils/ostream.h>
#ifndef NDEBUG
# include <string>
# include <utils/CString.h>
# include <string_view>
#endif
#include <stddef.h>
@@ -36,11 +37,11 @@ static char const * const kOurNamespace = "filament::backend::";
// removes all occurrences of "what" from "str"
UTILS_NOINLINE
static std::string& removeAll(std::string& str, const std::string& what) noexcept {
static CString& removeAll(CString& str, const std::string_view what) noexcept {
if (!what.empty()) {
const std::string empty;
const CString empty;
size_t pos = 0;
while ((pos = str.find(what, pos)) != std::string::npos) {
while ((pos = std::string_view{ str.data(), str.size() }.find(what, pos)) != std::string_view::npos) {
str.replace(pos, what.length(), empty);
}
}
@@ -49,13 +50,13 @@ static std::string& removeAll(std::string& str, const std::string& what) noexcep
template <typename T>
UTILS_NOINLINE
static io::ostream& logHandle(io::ostream& out, std::string& typeName, T id) noexcept {
static io::ostream& logHandle(io::ostream& out, CString& typeName, T id) noexcept {
return out << removeAll(typeName, kOurNamespace) << " @ " << id;
}
template <typename T>
io::ostream& operator<<(io::ostream& out, const Handle<T>& h) noexcept {
std::string s(CallStack::typeName<Handle<T>>().c_str());
CString s{ CallStack::typeName<Handle<T>>() };
return logHandle(out, s, h.getId());
}

View File

@@ -46,7 +46,6 @@
#include <cctype>
#include <mutex>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
@@ -67,9 +66,9 @@ constexpr uint32_t MAX_NUM_SYNCHRONOUS_PROGRAMS_PER_FRAME = 1;
// ------------------------------------------------------------------------------------------------
static std::string to_string(bool const b) { return b ? "true" : "false"; }
static std::string to_string(int const i) { return std::to_string(i); }
static std::string to_string(float const f) { return "float(" + std::to_string(f) + ")"; }
static CString to_string(bool const b) { return CString{ b ? "true" : "false" }; }
static CString to_string(int const i) { return utils::to_string(i); }
static CString to_string(float const f) { return "float(" + utils::to_string(f) + ")"; }
static void logCompilationError(ShaderStage shaderType, const char* name, GLuint shaderId,
Program::ShaderBlob const& sourceCode) noexcept;
@@ -696,14 +695,14 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons
bool multiview, program_token_t const& token) noexcept {
FILAMENT_TRACING_CALL(FILAMENT_TRACING_CATEGORY_FILAMENT);
auto const appendSpecConstantString = +[](std::string& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + std::to_string(sc.id) + ' ';
auto const appendSpecConstantString = +[](CString& s, Program::SpecializationConstant const& sc) {
s += "#define SPIRV_CROSS_CONSTANT_ID_" + utils::to_string(sc.id) + ' ';
s += std::visit([](auto&& arg) { return to_string(arg); }, sc.value);
s += '\n';
return s;
};
std::string specializationConstantString;
CString specializationConstantString;
int32_t numViews = 2;
for (auto const& sc: specializationConstants) {
appendSpecConstantString(specializationConstantString, sc);
@@ -766,8 +765,10 @@ void ShaderCompilerService::cancelPendingSynchronousProgram(program_token_t cons
}
std::array<std::string_view, 5> sources = {
version, prolog, specializationConstantString, packingFunctions,
{ body.data(), body.size() - 1 }// null-terminated
version, prolog,
{ specializationConstantString.data(), specializationConstantString.size() },
packingFunctions,
{ body.data(), body.size() - 1 } // null-terminated
};
// Some of the sources may be zero-length. Remove them as to avoid passing lengths of
@@ -966,16 +967,18 @@ UTILS_NOINLINE
sourceCode.size() };
size_t lc = 1;
size_t start = 0;
std::string line;
CString line;
while (true) {
size_t const end = shader.find('\n', start);
if (end == std::string::npos) {
line = shader.substr(start);
if (end == std::string_view::npos) {
auto sv = shader.substr(start);
line = { sv.data(), sv.size() };
} else {
line = shader.substr(start, end - start);
auto sv = shader.substr(start, end - start);
line = { sv.data(), sv.size() };
}
LOG(ERROR) << lc++ << ": " << line.c_str();
if (end == std::string::npos) {
if (end == std::string_view::npos) {
break;
}
start = end + 1;

View File

@@ -24,6 +24,7 @@
#include "VulkanConstants.h"
#include "VulkanContext.h"
#include <utils/CString.h>
#include <utils/Log.h>
#include <utils/Panic.h>
#include <utils/debug.h>
@@ -57,26 +58,26 @@ VkCommandBuffer createCommandBuffer(VkDevice device, VkCommandPool pool) {
} // anonymous namespace
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
void VulkanGroupMarkers::push(std::string const& marker, Timestamp start) noexcept {
void VulkanGroupMarkers::push(CString const& marker, Timestamp start) noexcept {
mMarkers.push_back({marker,
start.time_since_epoch().count() > 0.0
? start
: std::chrono::high_resolution_clock::now()});
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop() noexcept {
std::pair<CString, Timestamp> VulkanGroupMarkers::pop() noexcept {
auto ret = mMarkers.back();
mMarkers.pop_back();
return ret;
}
std::pair<std::string, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
std::pair<CString, Timestamp> VulkanGroupMarkers::pop_bottom() noexcept {
auto ret = mMarkers.front();
mMarkers.pop_front();
return ret;
}
std::pair<std::string, Timestamp> const& VulkanGroupMarkers::top() const {
std::pair<CString, Timestamp> const& VulkanGroupMarkers::top() const {
assert_invariant(!empty());
return mMarkers.back();
}
@@ -353,7 +354,7 @@ void CommandBufferPool::waitFor(VkSemaphore previousAction, VkPipelineStageFlags
}
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::string CommandBufferPool::topMarker() const {
CString CommandBufferPool::topMarker() const {
if (!mGroupMarkers || mGroupMarkers->empty()) {
return "";
}
@@ -364,11 +365,11 @@ void CommandBufferPool::pushMarker(char const* marker, VulkanGroupMarkers::Times
if (!mGroupMarkers) {
mGroupMarkers = std::make_unique<VulkanGroupMarkers>();
}
mGroupMarkers->push(marker, timestamp);
mGroupMarkers->push(CString{ marker }, timestamp);
getRecording().pushMarker(marker);
}
std::pair<std::string, VulkanGroupMarkers::Timestamp> CommandBufferPool::popMarker() {
std::pair<CString, VulkanGroupMarkers::Timestamp> CommandBufferPool::popMarker() {
assert_invariant(mGroupMarkers && !mGroupMarkers->empty());
auto ret = mGroupMarkers->pop();
@@ -528,7 +529,7 @@ void VulkanCommands::insertEventMarker(char const* str, uint32_t len) {
}
}
std::string VulkanCommands::getTopGroupMarker() const {
CString VulkanCommands::getTopGroupMarker() const {
if (mProtectedPool) {
return mProtectedPool->topMarker();
}

View File

@@ -28,6 +28,7 @@
#include "vulkan/utils/StaticVector.h"
#include <utils/Condition.h>
#include <utils/CString.h>
#include <utils/FixedCapacityVector.h>
#include <utils/Mutex.h>
@@ -35,7 +36,6 @@
#include <chrono>
#include <list>
#include <string>
#include <utility>
namespace filament::backend {
@@ -47,14 +47,14 @@ class VulkanGroupMarkers {
public:
using Timestamp = std::chrono::time_point<std::chrono::high_resolution_clock>;
void push(std::string const& marker, Timestamp start = {}) noexcept;
std::pair<std::string, Timestamp> pop() noexcept;
std::pair<std::string, Timestamp> pop_bottom() noexcept;
std::pair<std::string, Timestamp> const& top() const;
void push(utils::CString const& marker, Timestamp start = {}) noexcept;
std::pair<utils::CString, Timestamp> pop() noexcept;
std::pair<utils::CString, Timestamp> pop_bottom() noexcept;
std::pair<utils::CString, Timestamp> const& top() const;
bool empty() const noexcept;
private:
std::list<std::pair<std::string, Timestamp>> mMarkers;
std::list<std::pair<utils::CString, Timestamp>> mMarkers;
};
#endif // FVK_DEBUG_GROUP_MARKERS
@@ -148,9 +148,9 @@ struct CommandBufferPool {
void waitFor(VkSemaphore previousAction, VkPipelineStageFlags waitStage);
#if FVK_ENABLED(FVK_DEBUG_GROUP_MARKERS)
std::string topMarker() const;
utils::CString topMarker() const;
void pushMarker(char const* marker, VulkanGroupMarkers::Timestamp timestamp);
std::pair<std::string, VulkanGroupMarkers::Timestamp> popMarker();
std::pair<utils::CString, VulkanGroupMarkers::Timestamp> popMarker();
void insertEvent(char const* marker);
#endif
@@ -249,7 +249,7 @@ public:
void pushGroupMarker(char const* str, VulkanGroupMarkers::Timestamp timestamp = {});
void popGroupMarker();
void insertEventMarker(char const* string, uint32_t len);
std::string getTopGroupMarker() const;
utils::CString getTopGroupMarker() const;
#endif
private:

View File

@@ -32,6 +32,7 @@
#include <utils/compiler.h> // UTILS_FALLTHROUGH
#include <utils/Panic.h> // ASSERT_POSTCONDITION
#include <utils/CString.h>
using namespace bluevk;
@@ -285,7 +286,7 @@ VulkanProgram::VulkanProgram(VkDevice device, Program const& builder) noexcept
<< " error=" << static_cast<int32_t>(result);
#if FVK_ENABLED(FVK_DEBUG_DEBUG_UTILS)
std::string name{ builder.getName().c_str(), builder.getName().size() };
utils::CString name{ builder.getName().c_str(), builder.getName().size() };
switch (static_cast<ShaderStage>(i)) {
case ShaderStage::VERTEX:
name += "_vs";

View File

@@ -95,7 +95,7 @@ ResourceType getTypeEnum() noexcept {
return ResourceType::UNDEFINED_TYPE;
}
std::string getTypeStr(ResourceType type) {
std::string_view getTypeStr(ResourceType type) {
switch (type) {
case ResourceType::BUFFER_OBJECT:
return "BufferObject";

View File

@@ -58,7 +58,7 @@ enum class ResourceType : uint8_t {
template<typename D>
ResourceType getTypeEnum() noexcept;
std::string getTypeStr(ResourceType type);
std::string_view getTypeStr(ResourceType type);
inline bool isThreadSafeType(ResourceType type) {
return type == ResourceType::FENCE || type == ResourceType::TIMER_QUERY;

View File

@@ -86,9 +86,9 @@ static std::unique_ptr<MaterialParser> createParser(Backend const backend,
MaterialParser::ParseResult const materialResult = materialParser->parse();
if (UTILS_UNLIKELY(materialResult == MaterialParser::ParseResult::ERROR_MISSING_BACKEND)) {
std::string languageNames;
CString languageNames;
for (auto it = languages.begin(); it != languages.end(); ++it) {
languageNames.append(shaderLanguageToString(*it));
languageNames.append(CString{shaderLanguageToString(*it)});
if (std::next(it) != languages.end()) {
languageNames.append(", ");
}

View File

@@ -18,7 +18,13 @@
#include <private/utils/Tracing.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <utils/ostream.h>
#include <iterator>
#include <cstdint>
namespace filament {
@@ -254,25 +260,23 @@ char const* DependencyGraph::Node::getName() const noexcept {
utils::CString DependencyGraph::Node::graphvizify() const noexcept {
#ifndef NDEBUG
std::string s;
s.reserve(128);
utils::CString s;
uint32_t id = getId();
uint32_t const id = getId();
const char* const nodeName = getName();
uint32_t const refCount = getRefCount();
s.append("[label=\"");
s.append(nodeName);
s.append("\\nrefs: ");
s.append(std::to_string(refCount));
s.append(utils::to_string(refCount));
s.append(", id: ");
s.append(std::to_string(id));
s.append(utils::to_string(id));
s.append("\", style=filled, fillcolor=");
s.append(refCount ? "skyblue" : "skyblue4");
s.append("]");
s.shrink_to_fit();
return utils::CString{ s.c_str() };
return s;
#else
return {};
#endif

View File

@@ -547,21 +547,16 @@ fgviewer::FrameGraphInfo FrameGraph::getFrameGraphInfo(const char *viewName) con
// resource type so it works
auto descriptor = static_cast<Resource<FrameGraphTexture> const*>(
getResource(resourceHandle))->descriptor;
emplace_resource_property("width",
utils::CString(std::to_string(descriptor.width).data()));
emplace_resource_property("height",
utils::CString(std::to_string(descriptor.height).data()));
emplace_resource_property("depth",
utils::CString(std::to_string(descriptor.depth).data()));
emplace_resource_property("format",
utils::to_string(descriptor.format));
emplace_resource_property("width", utils::to_string(descriptor.width));
emplace_resource_property("height", utils::to_string(descriptor.height));
emplace_resource_property("depth", utils::to_string(descriptor.depth));
emplace_resource_property("format", utils::to_string(descriptor.format));
};
if (resourceNode->getParentNode() != nullptr) {
emplace_resource_property("is_subresource_of",
utils::CString(std::to_string(
resourceNode->getParentHandle().index).data()));
utils::to_string(resourceNode->getParentHandle().index));
}
emplace_resource_descriptor(resourceHandle);
resources.emplace(resourceHandle.index, fgviewer::FrameGraphInfo::Resource(

View File

@@ -23,6 +23,10 @@
#include <details/Texture.h>
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <string>
using namespace filament::backend;
@@ -294,7 +298,7 @@ RenderPassNode::RenderPassData const* RenderPassNode::getRenderPassData(uint32_t
utils::CString RenderPassNode::graphvizify() const noexcept {
#ifndef NDEBUG
std::string s;
utils::CString s;
uint32_t const id = getId();
const char* const nodeName = getName();
@@ -303,17 +307,17 @@ utils::CString RenderPassNode::graphvizify() const noexcept {
s.append("[label=\"");
s.append(nodeName);
s.append("\\nrefs: ");
s.append(std::to_string(refCount));
s.append(utils::to_string(refCount));
s.append(", id: ");
s.append(std::to_string(id));
s.append(utils::to_string(id));
for (auto const& rt :mRenderTargetData) {
s.append("\\nS:");
s.append(utils::to_string(rt.backend.params.flags.discardStart).c_str());
s.append(utils::to_string(rt.backend.params.flags.discardStart));
s.append(", E:");
s.append(utils::to_string(rt.backend.params.flags.discardEnd).c_str());
s.append(utils::to_string(rt.backend.params.flags.discardEnd));
s.append(", C:");
s.append(utils::to_string(rt.backend.params.flags.clear).c_str());
s.append(utils::to_string(rt.backend.params.flags.clear));
}
s.append("\", ");
@@ -322,7 +326,7 @@ utils::CString RenderPassNode::graphvizify() const noexcept {
s.append(refCount ? "darkorange" : "darkorange4");
s.append("]");
return utils::CString{ s.c_str() };
return s;
#else
return {};
#endif
@@ -342,14 +346,12 @@ char const* PresentPassNode::getName() const noexcept {
utils::CString PresentPassNode::graphvizify() const noexcept {
#ifndef NDEBUG
std::string s;
s.reserve(128);
utils::CString s;
uint32_t const id = getId();
s.append("[label=\"Present , id: ");
s.append(std::to_string(id));
s.append(utils::to_string(id));
s.append("\", style=filled, fillcolor=red3]");
s.shrink_to_fit();
return utils::CString{ s.c_str() };
return s;
#else
return {};
#endif

View File

@@ -14,10 +14,21 @@
* limitations under the License.
*/
#include "FrameGraphId.h"
#include "details/DependencyGraph.h"
#include "fg/FrameGraph.h"
#include "fg/details/PassNode.h"
#include "fg/details/ResourceNode.h"
#include <utils/compiler.h>
#include <utils/debug.h>
#include <utils/CString.h>
#include <new>
#include <cstdint>
namespace filament {
ResourceNode::ResourceNode(FrameGraph& fg, FrameGraphHandle const h, FrameGraphHandle const parent) noexcept
@@ -105,20 +116,20 @@ bool ResourceNode::hasWriteFrom(PassNode const* node) const noexcept {
void ResourceNode::setParentReadDependency(ResourceNode* parent) noexcept {
if (!mParentReadEdge) {
mParentReadEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), parent, this);
mParentReadEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), parent, this);
}
}
void ResourceNode::setParentWriteDependency(ResourceNode* parent) noexcept {
if (!mParentWriteEdge) {
mParentWriteEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), this, parent);
mParentWriteEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), this, parent);
}
}
void ResourceNode::setForwardResourceDependency(ResourceNode* source) noexcept {
assert_invariant(!mForwardedEdge);
mForwardedEdge = new DependencyGraph::Edge(mFrameGraph.getGraph(), this, source);
mForwardedEdge = new(std::nothrow) DependencyGraph::Edge(mFrameGraph.getGraph(), this, source);
}
@@ -132,24 +143,23 @@ void ResourceNode::resolveResourceUsage(DependencyGraph& graph) noexcept {
utils::CString ResourceNode::graphvizify() const noexcept {
#ifndef NDEBUG
std::string s;
s.reserve(128);
utils::CString s;
uint32_t const id = getId();
const char* const nodeName = getName();
VirtualResource* const pResource = mFrameGraph.getResource(resourceHandle);
VirtualResource const* const pResource = mFrameGraph.getResource(resourceHandle);
FrameGraph::ResourceSlot const& slot = mFrameGraph.getResourceSlot(resourceHandle);
s.append("[label=\"");
s.append(nodeName);
s.append("\\nrefs: ");
s.append(std::to_string(pResource->refcount));
s.append(utils::to_string(pResource->refcount));
s.append(", id: ");
s.append(std::to_string(id));
s.append(utils::to_string(id));
s.append("\\nversion: ");
s.append(std::to_string(resourceHandle.version));
s.append(utils::to_string(resourceHandle.version));
s.append("/");
s.append(std::to_string(slot.version));
s.append(utils::to_string(slot.version));
if (pResource->isImported()) {
s.append(", imported");
}
@@ -160,9 +170,8 @@ utils::CString ResourceNode::graphvizify() const noexcept {
s.append("style=filled, fillcolor=");
s.append(pResource->refcount ? "skyblue" : "skyblue4");
s.append("]");
s.shrink_to_fit();
return utils::CString{ s.c_str() };
return s;
#else
return {};
#endif

View File

@@ -22,6 +22,7 @@
#include <utils/compiler.h>
#include <string_view>
#include <type_traits>
#include <utility>
#include <assert.h>
@@ -51,6 +52,10 @@ struct hashCStrings {
template <size_t N>
using StringLiteral = const char[N];
namespace details {
template<typename T>
constexpr bool is_char_pointer_v = std::is_pointer_v<T> && std::is_same_v<char, std::remove_cv_t<std::remove_pointer_t<T>>>;
} // namespace details
// ------------------------------------------------------------------------------------------------
@@ -127,8 +132,7 @@ public:
// replace
template<size_t N>
CString& replace(size_type const pos,
size_type const len, const StringLiteral<N>& str) & noexcept {
CString& replace(size_type const pos, size_type const len, const StringLiteral<N>& str) & noexcept {
return replace(pos, len, str, N - 1);
}
@@ -136,36 +140,83 @@ public:
return replace(pos, len, str.c_str_safe(), str.size());
}
template <typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString& replace(size_type pos, size_type len, T str) & noexcept {
if (str) {
return replace(pos, len, str, strlen(str));
}
return replace(pos, len, "", 0);
}
template<size_t N>
CString&& replace(size_type const pos,
size_type const len, const StringLiteral<N>& str) && noexcept {
return std::move(replace(pos, len, str));
CString&& replace(size_type pos, size_type len, const StringLiteral<N>& str) && noexcept {
this->replace(pos, len, str);
return std::move(*this);
}
CString&& replace(size_type const pos, size_type const len, const CString& str) && noexcept {
return std::move(replace(pos, len, str));
this->replace(pos, len, str);
return std::move(*this);
}
template <typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString&& replace(size_type pos, size_type len, T str) && noexcept {
this->replace(pos, len, str);
return std::move(*this);
}
// insert
CString& insert(size_type pos, char c) & noexcept {
const char s[1] = { c };
return replace(pos, 0, s, 1);
}
template<size_t N>
CString& insert(size_type const pos, const StringLiteral<N>& str) & noexcept {
return replace(pos, 0, str);
return replace(pos, 0, str, N - 1);
}
CString& insert(size_type const pos, const CString& str) & noexcept {
return replace(pos, 0, str);
return replace(pos, 0, str.c_str_safe(), str.size());
}
template <typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString& insert(size_type pos, T str) & noexcept {
if (str) {
return replace(pos, 0, str, strlen(str));
}
return *this;
}
CString&& insert(size_type pos, char c) && noexcept {
this->insert(pos, c);
return std::move(*this);
}
template<size_t N>
CString&& insert(size_type const pos, const StringLiteral<N>& str) && noexcept {
return std::move(*this).replace(pos, 0, str);
CString&& insert(size_type pos, const StringLiteral<N>& str) && noexcept {
this->insert(pos, str);
return std::move(*this);
}
CString&& insert(size_type const pos, const CString& str) && noexcept {
return std::move(*this).replace(pos, 0, str);
this->insert(pos, str);
return std::move(*this);
}
template <typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString&& insert(size_type pos, T str) && noexcept {
this->insert(pos, str);
return std::move(*this);
}
// append
CString& append(char c) & noexcept {
return insert(length(), c);
}
template<size_t N>
CString& append(const StringLiteral<N>& str) & noexcept {
return insert(length(), str);
@@ -175,15 +226,49 @@ public:
return insert(length(), str);
}
template<typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString& append(T str) & noexcept {
return insert(length(), str);
}
CString&& append(char c) && noexcept {
this->append(c);
return std::move(*this);
}
template<size_t N>
CString&& append(const StringLiteral<N>& str) && noexcept {
return std::move(*this).insert(length(), str);
this->append(str);
return std::move(*this);
}
CString&& append(const CString& str) && noexcept {
return std::move(*this).insert(length(), str);
this->append(str);
return std::move(*this);
}
template<typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString&& append(T str) && noexcept {
this->append(str);
return std::move(*this);
}
// operator+=
CString& operator+=(char c) & noexcept {
return append(c);
}
CString& operator+=(const CString& str) & noexcept {
return append(str);
}
template<size_t N>
CString& operator+=(const StringLiteral<N>& str) & noexcept {
return append(str);
}
template <typename T, typename = std::enable_if_t<details::is_char_pointer_v<T>>>
CString& operator+=(T str) & noexcept {
return append(str);
}
const_reference operator[](size_type const pos) const noexcept {
assert(pos < size());
@@ -282,6 +367,32 @@ private:
}
};
// operator+
inline CString operator+(CString lhs, const CString& rhs) {
lhs += rhs;
return lhs;
}
inline CString operator+(CString lhs, const char* rhs) {
lhs += rhs;
return lhs;
}
inline CString operator+(const char* lhs, CString rhs) {
rhs.insert(0, lhs);
return rhs;
}
inline CString operator+(CString lhs, char rhs) {
lhs += rhs;
return lhs;
}
inline CString operator+(char lhs, CString rhs) {
rhs.insert(0, lhs);
return rhs;
}
// Implement this for your type for automatic conversion to CString. Failing to do so leads
// to a compile-time failure.
template<typename T>

View File

@@ -377,7 +377,8 @@ public:
* @param file the file where the above function in implemented
* @param line the line in the above file where the error was detected
* @param literal a literal version of the error message
* @param format printf style string describing the error
* @param format printf style format string describing the error
* @param ... printf style arguments
* @see ASSERT_PRECONDITION, ASSERT_POSTCONDITION, ASSERT_ARITHMETIC
* @see PANIC_PRECONDITION, PANIC_POSTCONDITION, PANIC_ARITHMETIC
* @see setMode()
@@ -448,8 +449,8 @@ void panicLog(
class UTILS_PUBLIC PreconditionPanic final : public TPanic<PreconditionPanic> {
// Programming error, can be avoided
// e.g.: invalid arguments
using TPanic<PreconditionPanic>::TPanic;
friend class TPanic<PreconditionPanic>;
using TPanic::TPanic;
friend class TPanic;
constexpr static auto type = "Precondition";
};
@@ -462,8 +463,8 @@ class UTILS_PUBLIC PreconditionPanic final : public TPanic<PreconditionPanic> {
class UTILS_PUBLIC PostconditionPanic final : public TPanic<PostconditionPanic> {
// Usually only detectable at runtime
// e.g.: deadlock would occur, arithmetic errors
using TPanic<PostconditionPanic>::TPanic;
friend class TPanic<PostconditionPanic>;
using TPanic::TPanic;
friend class TPanic;
constexpr static auto type = "Postcondition";
};
@@ -476,8 +477,8 @@ class UTILS_PUBLIC PostconditionPanic final : public TPanic<PostconditionPanic>
class UTILS_PUBLIC ArithmeticPanic final : public TPanic<ArithmeticPanic> {
// A common case of post-condition error
// e.g.: underflow, overflow, internal computations errors
using TPanic<ArithmeticPanic>::TPanic;
friend class TPanic<ArithmeticPanic>;
using TPanic::TPanic;
friend class TPanic;
constexpr static auto type = "Arithmetic";
};
@@ -547,7 +548,7 @@ public:
template<typename PanicType>
class FlagGuardedStream : public PanicStream {
public:
FlagGuardedStream(bool enable, char const* function, char const* file, int line,
FlagGuardedStream(bool const enable, char const* function, char const* file, int const line,
char const* condition)
: PanicStream(function, file, line, condition),
mEnablePanic(enable) {}

View File

@@ -20,7 +20,11 @@
#include <utils/ostream.h>
#include <algorithm>
#include <cassert>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
@@ -113,5 +117,73 @@ io::ostream& operator<<(io::ostream& out, const CString& rhs) {
}
#endif
} // namespace utils
namespace {
// use a C-style variadic function to avoid code bloat from templates
UTILS_NOINLINE
CString to_string_impl(const char* format, ...) noexcept {
va_list args1;
va_start(args1, format);
va_list args2;
va_copy(args2, args1);
int const len = vsnprintf(nullptr, 0, format, args1);
va_end(args1);
if (len >= 0) {
CString s(len);
vsnprintf(s.data(), len + 1, format, args2);
va_end(args2);
return s;
}
va_end(args2);
return {};
}
} // anonymous namespace
template<>
CString to_string<int>(int const value) noexcept {
return to_string_impl("%d", value);
}
template<>
CString to_string<unsigned int>(unsigned int const value) noexcept {
return to_string_impl("%u", value);
}
template<>
CString to_string<short>(short const value) noexcept {
return to_string_impl("%hd", value);
}
template<>
CString to_string<unsigned short>(unsigned short const value) noexcept {
return to_string_impl("%hu", value);
}
template<>
CString to_string<long>(long const value) noexcept {
return to_string_impl("%ld", value);
}
template<>
CString to_string<unsigned long>(unsigned long const value) noexcept {
return to_string_impl("%lu", value);
}
template<>
CString to_string<long long>(long long const value) noexcept {
return to_string_impl("%lld", value);
}
template<>
CString to_string<unsigned long long>(unsigned long long const value) noexcept {
return to_string_impl("%llu", value);
}
template<>
CString to_string<float>(float const value) noexcept {
return to_string_impl("%f", value);
}
} // namespace utils

View File

@@ -18,53 +18,416 @@
#include <utils/CString.h>
#include <algorithm>
#include <climits>
#include <utility>
using namespace utils;
TEST(CString, EmptyString) {
CString emptyString("");
CString const emptyString("");
EXPECT_STREQ("", emptyString.c_str_safe());
EXPECT_EQ(0, emptyString.length());
EXPECT_TRUE(emptyString.empty());
}
TEST(CString, Constructors) {
// CString()
{
CString str;
EXPECT_EQ(nullptr, str.c_str());
EXPECT_STREQ("", str.c_str_safe());
EXPECT_EQ(0, str.length());
EXPECT_TRUE(str.empty());
}
// CString(const char* cstr, size_t length)
{
CString str("foobar", 3);
EXPECT_STREQ("foo", str.c_str());
EXPECT_EQ(3, str.length());
}
// CString(size_t length)
{
CString str(5);
EXPECT_EQ(5, str.length());
// The memory is zero-initialized
EXPECT_EQ(0, str.c_str()[0]);
EXPECT_EQ(0, str.c_str()[4]);
}
// CString(const char* cstr)
{
CString str("hello");
EXPECT_STREQ("hello", str.c_str());
EXPECT_EQ(5, str.length());
}
// CString(StringLiteral<N>)
{
CString str("literal"); // this uses the template constructor
EXPECT_STREQ("literal", str.c_str());
EXPECT_EQ(7, str.length());
}
// Copy constructor
{
CString s1("copy me");
CString s2(s1);
EXPECT_STREQ("copy me", s2.c_str());
EXPECT_NE(s1.c_str(), s2.c_str()); // should be a deep copy
}
// Move constructor
{
CString s1("move me");
const char* original_cstr = s1.c_str();
CString s2(std::move(s1));
EXPECT_STREQ("move me", s2.c_str());
EXPECT_EQ(original_cstr, s2.c_str()); // pointer should be moved
EXPECT_EQ(nullptr, s1.c_str()); // original should be empty
}
}
TEST(CString, Assignment) {
// Copy assignment
{
CString s1("copy");
CString s2;
s2 = s1;
EXPECT_STREQ("copy", s2.c_str());
EXPECT_NE(s1.c_str(), s2.c_str());
}
// Move assignment
{
CString s1("move");
const char* original_cstr = s1.c_str();
CString s2;
s2 = std::move(s1);
EXPECT_STREQ("move", s2.c_str());
EXPECT_EQ(original_cstr, s2.c_str());
EXPECT_EQ(nullptr, s1.c_str());
}
// self-assignment
{
CString s1("self");
const char* original_cstr = s1.c_str();
s1 = s1;
EXPECT_STREQ("self", s1.c_str());
EXPECT_EQ(original_cstr, s1.c_str());
}
}
TEST(CString, Swap) {
CString s1("first");
CString s2("second");
const char* p1 = s1.c_str();
const char* p2 = s2.c_str();
size_t l1 = s1.length();
size_t l2 = s2.length();
s1.swap(s2);
EXPECT_STREQ("second", s1.c_str());
EXPECT_STREQ("first", s2.c_str());
EXPECT_EQ(p2, s1.c_str());
EXPECT_EQ(p1, s2.c_str());
EXPECT_EQ(l2, s1.length());
EXPECT_EQ(l1, s2.length());
}
TEST(CString, Concatenation) {
// operator+=
{
CString s("hello");
s += CString(" world");
EXPECT_STREQ("hello world", s.c_str());
}
{
CString s("hello");
s += " world";
EXPECT_STREQ("hello world", s.c_str());
}
{
CString s("hello");
const char* world = " world";
s += world;
EXPECT_STREQ("hello world", s.c_str());
}
{
CString s("hello");
s += "";
EXPECT_STREQ("hello", s.c_str());
}
{
CString s;
s += "world";
EXPECT_STREQ("world", s.c_str());
}
// operator+
{
CString s1("hello");
CString s2(" world");
CString s3 = s1 + s2;
EXPECT_STREQ("hello world", s3.c_str());
}
{
CString s1("hello");
CString s2 = s1 + " world";
EXPECT_STREQ("hello world", s2.c_str());
}
{
CString s1(" world");
CString s2 = "hello" + s1;
EXPECT_STREQ("hello world", s2.c_str());
}
{
CString s1("hello");
const char* world = " world";
CString s2 = s1 + world;
EXPECT_STREQ("hello world", s2.c_str());
}
{
CString s1(" world");
const char* hello = "hello";
CString s2 = hello + s1;
EXPECT_STREQ("hello world", s2.c_str());
}
{
CString s = CString("a") + CString("b") + "c" + "d";
EXPECT_STREQ("abcd", s.c_str());
}
}
TEST(CString, Comparison) {
CString s1("abc");
CString s2("abc");
CString s3("def");
CString s4("ab");
EXPECT_TRUE(s1 == s2);
EXPECT_FALSE(s1 == s3);
EXPECT_TRUE(s1 != s3);
EXPECT_FALSE(s1 != s2);
EXPECT_TRUE(s1 < s3);
EXPECT_FALSE(s3 < s1);
EXPECT_TRUE(s3 > s1);
EXPECT_FALSE(s1 > s3);
EXPECT_TRUE(s1 <= s2);
EXPECT_TRUE(s1 <= s3);
EXPECT_FALSE(s3 <= s1);
EXPECT_TRUE(s2 >= s1);
EXPECT_TRUE(s3 >= s1);
EXPECT_FALSE(s1 >= s3);
EXPECT_TRUE(s4 < s1);
EXPECT_TRUE(s1 > s4);
}
TEST(CString, ElementAccess) {
CString str("01234");
const CString cstr("const");
EXPECT_EQ('0', str.front());
EXPECT_EQ('4', str.back());
EXPECT_EQ('c', cstr.front());
EXPECT_EQ('t', cstr.back());
EXPECT_EQ('2', str[2]);
EXPECT_EQ('n', cstr[2]);
str[0] = 'A';
EXPECT_STREQ("A1234", str.c_str());
EXPECT_EQ('3', str.at(3));
EXPECT_EQ('t', cstr.at(4));
// iterators
std::string s(str.begin(), str.end());
EXPECT_EQ("A1234", s);
EXPECT_TRUE(std::equal(cstr.begin(), cstr.end(), "const"));
}
TEST(CString, Append) {
// Append CString
{
CString str("foo");
str.append(CString("bar"));
EXPECT_STREQ("foobar", str.c_str());
}
// Append string literal
{
CString str("foo");
str.append("bar");
EXPECT_STREQ("foobar", str.c_str());
}
// Append const char*
{
CString str("foo");
const char* bar = "bar";
str.append(bar);
EXPECT_STREQ("foobar", str.c_str());
}
// Append nullptr
{
CString str("foo");
const char* bar = nullptr;
str.append(bar);
EXPECT_STREQ("foo", str.c_str());
}
// Append to empty
{
CString str;
str.append("foo");
EXPECT_STREQ("foo", str.c_str());
}
{
CString str;
str.append(CString("foo"));
EXPECT_STREQ("foo", str.c_str());
}
{
CString str;
const char* foo = "foo";
str.append(foo);
EXPECT_STREQ("foo", str.c_str());
}
// Append empty
{
CString str("foo");
str.append("");
EXPECT_STREQ("foo", str.c_str());
}
{
CString str("foo");
str.append(CString(""));
EXPECT_STREQ("foo", str.c_str());
}
{
CString str("foo");
const char* empty = "";
str.append(empty);
EXPECT_STREQ("foo", str.c_str());
}
// Chaining
{
CString str;
str.append("foo").append("bar").append(CString("baz"));
EXPECT_STREQ("foobarbaz", str.c_str());
}
// r-value appends
{
CString str = CString("foo").append("bar");
EXPECT_STREQ("foobar", str.c_str());
}
}
TEST(CString, Insert) {
// with CString
{
CString str("foobaz");
str.insert(3, CString("bar"));
EXPECT_STREQ("foobarbaz", str.c_str());
}
// with string literal
{
CString str("world");
str.insert(0, "hello ");
EXPECT_STREQ("hello world", str.c_str());
}
// with const char*
{
CString str("foo");
const char* bar = "bar";
str.insert(3, bar);
EXPECT_STREQ("foobar", str.c_str());
}
// with nullptr
{
CString str("foo");
const char* bar = nullptr;
str.insert(1, bar);
EXPECT_STREQ("foo", str.c_str());
}
// r-value insert
{
const char* bar = "bar";
CString str = CString("foo").insert(3, bar);
EXPECT_STREQ("foobar", str.c_str());
}
}
TEST(CString, Replace) {
{
CString str("foo bar baz");
str.replace(0, 0, CString("lkj"));
EXPECT_STREQ("lkjfoo bar baz", str.c_str());
}
// with CString
{
CString str("foo bar baz");
str.replace(4, 3, CString("dpa"));
EXPECT_STREQ("foo dpa baz", str.c_str());
}
// with string literal
{
CString str("foo bar baz");
str.replace(4, 3, CString(""));
str.replace(4, 3, "dpa");
EXPECT_STREQ("foo dpa baz", str.c_str());
}
// with const char*
{
CString str("foo bar baz");
const char* dpa = "dpa";
str.replace(4, 3, dpa);
EXPECT_STREQ("foo dpa baz", str.c_str());
}
// with nullptr
{
CString str("foo bar baz");
const char* dpa = nullptr;
str.replace(4, 3, dpa);
EXPECT_STREQ("foo baz", str.c_str());
}
// with empty string
{
CString str("foo bar baz");
str.replace(4, 3, CString("a"));
EXPECT_STREQ("foo a baz", str.c_str());
str.replace(4, 3, "");
EXPECT_STREQ("foo baz", str.c_str());
}
// replace that grows the string
{
CString str("foo bar baz");
str.replace(4, 3, CString("abcdef"));
str.replace(4, 3, "abcdef");
EXPECT_STREQ("foo abcdef baz", str.c_str());
}
// replace that shrinks the string
{
CString str("foo bar baz");
str.replace(0, 3, CString("abcdef"));
EXPECT_STREQ("abcdef bar baz", str.c_str());
str.replace(4, 3, "a");
EXPECT_STREQ("foo a baz", str.c_str());
}
// replace at the beginning
{
CString str("foo bar baz");
str.replace(8, 3, CString("abcdef"));
EXPECT_STREQ("foo bar abcdef", str.c_str());
str.replace(0, 3, "lkj");
EXPECT_STREQ("lkj bar baz", str.c_str());
}
// replace at the end
{
CString str("foo bar baz");
str.replace(0, 11, CString("abcdef"));
EXPECT_STREQ("abcdef", str.c_str());
str.replace(8, 3, "lkj");
EXPECT_STREQ("foo bar lkj", str.c_str());
}
// replace all
{
CString str("foo bar baz");
str.replace(0, 11, "lkj");
EXPECT_STREQ("lkj", str.c_str());
}
// r-value replace
{
const char* dpa = "dpa";
CString str = CString("foo bar baz").replace(4, 3, dpa);
EXPECT_STREQ("foo dpa baz", str.c_str());
}
}
@@ -94,6 +457,42 @@ TEST(CString, ReplacePastEndOfString) {
}
}
TEST(CString, ToString) {
EXPECT_STREQ("0", to_string(0).c_str());
EXPECT_STREQ("123", to_string(123).c_str());
EXPECT_STREQ("-456", to_string(-456).c_str());
EXPECT_STREQ("2147483647", to_string(INT_MAX).c_str());
EXPECT_STREQ("-2147483648", to_string(INT_MIN).c_str());
EXPECT_STREQ("0", to_string(0u).c_str());
EXPECT_STREQ("4294967295", to_string(UINT_MAX).c_str());
EXPECT_STREQ("0", to_string((short)0).c_str());
EXPECT_STREQ("32767", to_string(SHRT_MAX).c_str());
EXPECT_STREQ("-32768", to_string(SHRT_MIN).c_str());
EXPECT_STREQ("0", to_string((unsigned short)0).c_str());
EXPECT_STREQ("65535", to_string(USHRT_MAX).c_str());
#if LONG_MAX == 2147483647
EXPECT_STREQ("2147483647", to_string(LONG_MAX).c_str());
EXPECT_STREQ("-2147483648", to_string(LONG_MIN).c_str());
EXPECT_STREQ("4294967295", to_string(ULONG_MAX).c_str());
#else
EXPECT_STREQ("9223372036854775807", to_string(LONG_MAX).c_str());
EXPECT_STREQ("-9223372036854775808", to_string(LONG_MIN).c_str());
EXPECT_STREQ("18446744073709551615", to_string(ULONG_MAX).c_str());
#endif
EXPECT_STREQ("9223372036854775807", to_string(LLONG_MAX).c_str());
EXPECT_STREQ("-9223372036854775808", to_string(LLONG_MIN).c_str());
EXPECT_STREQ("18446744073709551615", to_string(ULLONG_MAX).c_str());
EXPECT_STREQ("0.000000", to_string(0.0f).c_str());
EXPECT_STREQ("1.500000", to_string(1.5f).c_str());
EXPECT_STREQ("-3.140000", to_string(-3.14f).c_str());
}
TEST(FixedSizeString, EmptyString) {
{
FixedSizeString<32> str;