Как добавить опциональную поддержку сторонней библиотеки?
У меня есть класс, принимающий в конструкторе матрицы моей же библиотеки:
struct MatrixSolver {
MatrixSolver(const my::Matrix& mtx);
}
Теперь я хочу добавить поддержку реализации матриц сторонней библиотеки для удобства пользователей, например, Eigen::MatrixXd. В будущем могут понадобиться и другие библиотеки. Если просто добавить конструктор с этим параметром, то мой MatrixSolver получит безусловную зависимость Eigen и люди, у которых не поставлен Eigen, не смогут его использовать. Как сделать опциональную поддержку этой библиотеки?
Если просто сделать произвольный шаблонный параметр и использовать методы Eigen::MatrixXd, то возникают следующие проблемы: 1) как добавить поддержку другой библиотеки в будущем?; 2) невнятные ошибки при использовании не Eigen::MatrixXd этим конструктором; 3) нет поддержки преобразуемых в Eigen::MatrixXd типов, только непосредственно таких, члены которых используются в шаблоне.
Ещё один известный вариант — сделать проверку #ifdef EIGEN_INCLUDE_GUARD и только тогда добавлять конструктор. Но с этим вариантом возникает проблема с порядком включения хедеров:
#include "eigen.h"
#include "matrix_solver.h"
не то же самое, что и
#include "eigen.h"
#include "matrix_solver.h"
И ещё есть вариант с явным указанием, как компилировать мою библиотеку: -DSOLVER_WITH_EIGEN и проверкой этого условия. Работает нормально, но проблема в явном указании и перекомпиляции библиотеки для разных пользователей, чего хотелось бы избежать.
Ответы (2 шт):
Просто вынесите функции, предоставляющие поддержку Eigen, в отдельный заголовочный файл (обычно такие файлы помещают в каталог ext). Если пользователю будет нужно - он подключит этот файл.
Если у вашего класса есть метод, который который может принимать объекты Eigen, и его нельзя вынести в отдельный файл - делайте этот метод шаблонным, и используйте SFINAE/концепты. Например:
template<typename T, typename Enable = void>
struct matrix_traits{
static constexpr bool is_matrix = false;
};
class MyClass{
template<typename Matrix, std::enable_if_t<matrix_traits<std::decay_t<Matrix>>::is_matrix, bool> = true>
void setMatrix(Matrix&& matrix){
using traits = matrix_traits<std::decay_t<Matrix>>;
std::size_t rowCount = traits::rowCount(matrix);
std::size_t colCount = traits::colCount(matrix);
}
};
А затем в ext/eigen.hpp добавте специализацию:
template<> struct matrix_traits<Eigen::MatrixXd>{ // лучше поддерживать шаблонную матрицу Eigen, но я не помню, как она выглядит
static constexpr bool is_matrix = true;
static std::size_t rowCount(const Eigen::MatrixXd& m){
return m.rows();
}
static std::size_t colCount(const Eigen::MatrixXd& m){
return m.cols();
}
}
UPD: один из вариантов поддержки шаблонных классов Eigen:
namespace my_ns{
template<class D>
std::true_type is_eigen_matrix(const Eigen::MatrixBase<D>&){ return{}; }
template<typename M> struct matrix_traits<
M, std::void_t<decltype(my_ns::is_eigen_matrix(std::declval<M>()))>>{
/* ... */
};
}
При перегрузке функций, подобных is_eigen_matrix, важно указать пространство имён, иначе случайно может подойти функция из другой библиотеки.
Другой вариант - использовать специализацию для std::is_constructable<Eigen::MatrixXd, M>::value. Но это плохой вариант, т.к. типы, преобразуемые в Eigen, могут не предоставлять нужный интерфейс, а если преобразовать этот тип в Eigen::MatrixXd, будет бессмысленное выделение динамической памяти.
Наличие стороннего заголовка можно проверить и затем действовать по обстоятельствам:
#if __has_include(<eigen.h>)
#include <eigen.h>
#define MYLIB_USES_EIGEN
#endif
...
struct MatrixSolver
{
#ifdef MYLIB_USES_EIGEN
MatrixSolver(Eigen::MatrixXd const & mat)
{
...
}
#endif