Last active
December 18, 2022 22:20
-
-
Save mtao/9423e672414990b2d6c03f46f0fd23d3 to your computer and use it in GitHub Desktop.
IGL + Concepts = Cleaner function declarations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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