简体   繁体   中英

Using a CRTP based class as an argument of a function, without knowing the template parameter

I am currently trying to write a differential equation solver and want to use classes as operators, which define an overloaded operator() so that I can use the operators as functors. The goal is that I have some data, lets take T for the sake of argument representing temperature, and I want to provide an interface like ddt(T) == laplacian(T), where ddt is the first order partial derivative with respect to time, and laplacian is the second order partial derivative with respect to space. In this example, I am simply solving a heat equation.

Since I want to allow for different time and space schemes, I want to have two base classes for time and space, and then derive my numerical schemes which approximate either time or space from those base classes, where my operator() is defined as a pure virtual function.

I want to be able to return a reference of the space operator from its operator(), so I use CRTP to specify the return type in the base class.

The time operator is on the left-hand side of the equation and is supposed to process all the information from the right-hand side. Therefore, the operator==, defined in the time class, receives an operator from the right-hand side. Here lies the problem: The argument of the operator== requires me to specify the type of the right-hand side operator I am passing in, however, I don't know which type I should use for the template argument as I want to accept different types of operators later. Given the code below, is there a clean way to get around this issue?

#include <iostream>
#include <vector>

using Vector = std::vector<double>;

template<class Type>
struct spaceOperatorBase {
    virtual Type operator()(Vector &data) = 0;
    protected:
    Vector _data;
};

struct laplacianOperator : public spaceOperatorBase<laplacianOperator> {
    laplacianOperator operator()(Vector &data) final override {
        std::cout << "solving laplacian operator" << std::endl;
        this->_data = data;
        return *this;
    }
};

template <class Type>
struct timeOperatorBase {
    virtual Type operator()(Vector &phi) = 0;
    virtual void operator==(const spaceOperatorBase<laplacianOperator> &rhs) = 0; // <- how to get rid here of the dependency on <laplacianOperator>?
};

struct eulerOperator : timeOperatorBase<eulerOperator> {

    eulerOperator operator()(Vector &phi) {
        std::cout << "preparing time-integration" << std::endl;
        return *this;
    };

    void operator==(const spaceOperatorBase<laplacianOperator> &rhs) { // <- how to get rid here of the dependency on <laplacianOperator>?
        std::cout << "solving equation" << std::endl;
    };
};

int main() {

Vector T;
laplacianOperator laplacian;
eulerOperator ddt;

ddt(T) == laplacian(T);

return 0;
}

I finally figured out a way that works for me. As I am using c++17, I can use std::variant and std::visit for the argument type of the virtual method. Here is the full code which is working for my needs. Posting it here for anyone googling a similar problem.

#include <iostream>
#include <vector>
#include <variant>

using Vector = std::vector<double>;

template<class Type>
struct spaceOperatorBase {
    virtual Type operator()(Vector &data) = 0;
    virtual void printMyType() const = 0;
    protected:
    Vector _data;
};

struct laplacianOperator : public spaceOperatorBase<laplacianOperator> {
    laplacianOperator operator()(Vector &data) final override {
        std::cout << "solving laplacian operator" << std::endl;
        this->_data = data;
        return *this;
    }
    void printMyType() const final override {
        std::cout << "I am a laplacian operator" << std::endl;
    }
};

// variant that holds all possible operators, can include multiple ...
using MyOperators = std::variant<laplacianOperator>;

template <class Type>
struct timeOperatorBase {
    virtual Type operator()(Vector &phi) = 0;
    virtual void operator==(const MyOperators &rhs) = 0; // <- how to get rid here of the dependency on <laplacianOperator>?
};

struct eulerOperator : timeOperatorBase<eulerOperator> {

    eulerOperator operator()(Vector &phi) {
        std::cout << "preparing time-integration" << std::endl;
        return *this;
    };

    void operator==(const MyOperators &rhs) { // <- how to get rid here of the dependency on <laplacianOperator>?
        std::cout << "solving equation" << std::endl;
        // accessing specific information from rhs
        auto operatorAccessor = [](const auto &accessor){
            accessor.printMyType();
        };
        std::visit(operatorAccessor, rhs);
    };
};

int main() {

Vector T;
laplacianOperator laplacian;
eulerOperator ddt;

ddt(T) == laplacian(T);

return 0;
}

This will print the following to the console

preparing time-integration
solving laplacian operator
solving equation
I am a laplacian operator

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM