Skip to content

Commit

Permalink
Add: Statically check for applicable variant visitor overloads
Browse files Browse the repository at this point in the history
  • Loading branch information
spnda committed Oct 26, 2024
1 parent 765492f commit ccff36a
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 21 deletions.
2 changes: 1 addition & 1 deletion include/fastgltf/tools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,7 @@ void copyComponentsFromAccessor(const Asset& asset, const Accessor& accessor, vo
* Computes the transform matrix for a given node, and multiplies the given base with that matrix.
*/
FASTGLTF_EXPORT inline auto getTransformMatrix(const Node& node, const math::fmat4x4& base = math::fmat4x4()) {
return std::visit(visitor {
return visit_exhaustive(visitor {
[&](const math::fmat4x4& matrix) {
return base * matrix;
},
Expand Down
8 changes: 4 additions & 4 deletions include/fastgltf/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1188,31 +1188,31 @@ namespace fastgltf {

template <typename F>
[[nodiscard]] auto and_then(F&& func)& {
using U = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<F, T&>>>;
using U = remove_cvref_t<std::invoke_result_t<F, T&>>;
if (!has_value())
return U(std::nullopt);
return std::invoke(std::forward<F>(func), **this);
}

template <typename F>
[[nodiscard]] auto and_then(F&& func) const& {
using U = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<F, const T&>>>;
using U = remove_cvref_t<std::invoke_result_t<F, const T&>>;
if (!has_value())
return U(std::nullopt);
return std::invoke(std::forward<F>(func), **this);
}

template <typename F>
[[nodiscard]] auto and_then(F&& func)&& {
using U = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<F, T>>>;
using U = remove_cvref_t<std::invoke_result_t<F, T>>;
if (!has_value())
return U(std::nullopt);
return std::invoke(std::forward<F>(func), std::move(**this));
}

template <typename F>
[[nodiscard]] auto and_then(F&& func) const&& {
using U = std::remove_cv_t<std::remove_reference_t<std::invoke_result_t<F, const T>>>;
using U = remove_cvref_t<std::invoke_result_t<F, const T>>;
if (!has_value())
return U(std::nullopt);
return std::invoke(std::forward<F>(func), std::move(**this));
Expand Down
26 changes: 26 additions & 0 deletions include/fastgltf/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,14 @@ namespace fastgltf {
return str.rfind(search, 0) == 0;
}

// Simple reimplementation of std::remove_cvref, which was only added in C++20.
template <typename T>
struct remove_cvref {
using type = std::remove_cv_t<std::remove_reference_t<T>>;
};
template <typename T>
using remove_cvref_t = typename remove_cvref<T>::type;

/**
* Helper type in order to allow building a visitor out of multiple lambdas within a call to
* std::visit
Expand All @@ -341,6 +349,24 @@ namespace fastgltf {

FASTGLTF_EXPORT template<class... Ts> visitor(Ts...) -> visitor<Ts...>;

template <typename Visitor, typename Variant, std::size_t... i>
constexpr bool is_exhaustive_visitor(std::integer_sequence<std::size_t, i...>) noexcept {
return std::conjunction_v<std::is_invocable<Visitor, std::variant_alternative_t<i, remove_cvref_t<Variant>>>...>;
}

/**
* Simple wrapper around std::visit for a single variant that checks at compile-time if the given visitor contains
* overloads for *all* required alternatives. This is meant to guarantee correctness, since using something like
* fastgltf::visitor could fail unexpectedly due to const-issues without any compile-time errors or warnings.
* @note This currently does not support auto parameters.
*/
FASTGLTF_EXPORT template<typename Visitor, typename Variant>
constexpr decltype(auto) visit_exhaustive(Visitor&& visitor, Variant&& variant) {
static_assert(is_exhaustive_visitor<Visitor, Variant>(std::make_index_sequence<std::variant_size_v<remove_cvref_t<Variant>>>()),
"The visitor does not include all necessary overloads for the given variant");
return std::visit(std::forward<Visitor>(visitor), std::forward<Variant>(variant));
}

// For simple ops like &, |, +, - taking a left and right operand.
#define FASTGLTF_ARITHMETIC_OP_TEMPLATE_MACRO(T1, T2, op) \
FASTGLTF_EXPORT constexpr T1 operator op(const T1& a, const T2& b) noexcept { \
Expand Down
32 changes: 16 additions & 16 deletions src/fastgltf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5050,8 +5050,8 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
}

if (!it->children.empty()) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("children":[)";
auto itc = it->children.begin();
while (itc != it->children.end()) {
Expand All @@ -5064,8 +5064,8 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
}

if (!it->weights.empty()) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("weights":[)";
auto itw = it->weights.begin();
while (itw != it->weights.end()) {
Expand All @@ -5077,27 +5077,27 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
json += ']';
}

std::visit(visitor {
visit_exhaustive(visitor {
[&](const TRS& trs) {
if (trs.rotation != math::fquat(0.f, 0.f, 0.f, 1.f)) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("rotation":[)";
json += std::to_string(trs.rotation[0]) + ',' + std::to_string(trs.rotation[1]) + ',' + std::to_string(trs.rotation[2]) + ',' + std::to_string(trs.rotation[3]);
json += "]";
}

if (trs.scale != math::fvec3(1.f)) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("scale":[)";
json += std::to_string(trs.scale[0]) + ',' + std::to_string(trs.scale[1]) + ',' + std::to_string(trs.scale[2]);
json += "]";
}

if (trs.translation != math::fvec3(0.f)) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("translation":[)";
json += std::to_string(trs.translation[0]) + ',' + std::to_string(trs.translation[1]) + ',' + std::to_string(trs.translation[2]);
json += "]";
Expand All @@ -5119,7 +5119,7 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
},
}, it->transform);

if (!it->instancingAttributes.empty() || it->lightIndex.has_value()) {
if (!it->instancingAttributes.empty() || it->lightIndex.has_value()) {
if (json.back() != '{') json += ',';
json += R"("extensions":{)";
if (!it->instancingAttributes.empty()) {
Expand All @@ -5136,8 +5136,8 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
if (json.back() != '{') json += ',';
json += R"("KHR_lights_punctual":{"light":)" + std::to_string(it->lightIndex.value()) + "}";
}
json += "}";
}
json += "}";
}

if (extrasWriteCallback != nullptr) {
auto extras = extrasWriteCallback(uabs(std::distance(asset.nodes.begin(), it)), fastgltf::Category::Nodes, userPointer);
Expand All @@ -5149,8 +5149,8 @@ void fg::Exporter::writeNodes(const Asset& asset, std::string& json) {
}

if (!it->name.empty()) {
if (json.back() != '{')
json += ',';
if (json.back() != '{')
json += ',';
json += R"("name":")" + fg::escapeString(it->name) + '"';
}
json += '}';
Expand Down

0 comments on commit ccff36a

Please sign in to comment.