Skip to content

Instantly share code, notes, and snippets.

@mtao
Last active December 18, 2022 22:20
Show Gist options
  • Save mtao/9423e672414990b2d6c03f46f0fd23d3 to your computer and use it in GitHub Desktop.
Save mtao/9423e672414990b2d6c03f46f0fd23d3 to your computer and use it in GitHub Desktop.
IGL + Concepts = Cleaner function declarations
#include <Eigen/Core>
#include <concepts>
#include <iostream>
// This example code shows how concepts could be used to lighten the syntax used
// in declaring igl style function input/outputs. In particular, inputs are
// typically derived from MatrixBase (which can be accessed directly) and
// outputs are derived from PlainObjectBase (which has memory to store values).
//
// Below there are three examples:
// assign, assign_auto, assign_igl_sugar, which show 3 different syntaxes for
// using constraints to check the validy of igl-style function input/outputs.
namespace concepts {
namespace detail {
// Eigen uses CRTP, so A is declared in the form
// > class A: public Base<A>, so A is derived from Base<A>.
template <typename T>
concept MatrixBaseDerived = std::derived_from<T, typename Eigen::MatrixBase<T>>;
template <typename T>
concept PlainObjectBaseDerived =
std::derived_from<T, typename Eigen::PlainObjectBase<T>>;
// We sanitize inputs to the above constraints for When the input passed is not
// the most derived type, which can happen when an igl-style function calls
// another igl-style function.
template <typename T>
using derived_type = std::decay_t<decltype(std::declval<T>().derived())>;
} // namespace detail
// Here's the most basic sort of constraint
template <typename T>
concept MatrixBaseDerived = detail::MatrixBaseDerived<detail::derived_type<T>>;
template <typename T>
concept PlainObjectBaseDerived =
detail::PlainObjectBaseDerived<detail::derived_type<T>>;
} // namespace concepts
// We can now declare the types in the template declaration rather than in the
// function declaration, which hopefully cleans the specification a bit
template <concepts::MatrixBaseDerived A, concepts::PlainObjectBaseDerived B>
void assign(const A& a, B& b) {
b = a;
}
// We can also hide the template declaration completely and move constraints
// into the function body
void assign_auto(const auto& a, auto& b) {
static_assert(concepts::MatrixBaseDerived<decltype(a)>);
static_assert(concepts::PlainObjectBaseDerived<decltype(b)>);
b = a;
}
// We can alias these to make the syntactic sugar a bit nicer
namespace concepts {
template <typename T>
concept IglInputType = MatrixBaseDerived<T>;
template <typename T>
concept IglOutputType = PlainObjectBaseDerived<T>;
} // namespace concepts
// With these convenience functions the intent of IGL style input/output are
// very clear
template <concepts::IglInputType A, concepts::IglOutputType B>
void assign_igl_sugar(const A& a, B& b) {
b = a;
}
int main(int argc, char* argv[]) {
auto a = Eigen::Matrix3d::Identity();
Eigen::MatrixXd b;
// basic example
assign(a, b); // passes
std::cout << b << std::endl; // outputs 1,0,0;0,1,0;0,0,1
assign_auto(a, b); // passes
std::cout << b << std::endl; // outputs 1,0,0;0,1,0;0,0,1
// we can also use a bit of syntactic sugar to further simplify things
assign_igl_sugar(a, b); // passes
std::cout << b << std::endl; // outputs 1,0,0;0,1,0;0,0,1
// a slightly more complex example
assign((a.array() + 1).matrix(), b); // passes
std::cout << b << std::endl; // outputs 2,1,1;1,2,1;1,1,2
// This doesn't work because the RHS is a read-only expression
// assign(a, b + a); // fails
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment