Как добавить опциональную поддержку сторонней библиотеки?

У меня есть класс, принимающий в конструкторе матрицы моей же библиотеки:

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 шт):

Автор решения: Ariox

Просто вынесите функции, предоставляющие поддержку 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, будет бессмысленное выделение динамической памяти.

→ Ссылка
Автор решения: user7860670

Наличие стороннего заголовка можно проверить и затем действовать по обстоятельствам:

#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
→ Ссылка