What would a CRTP-based solution to this look like?

让人想犯罪 __ 提交于 2019-11-28 10:45:51

问题


I asked the following question in this post (pasted below for convenience). One of the comments suggested that there is a CRTP-based solution to the problem. I am not able to figure out how CRTP is relevant here (well, I never used CRTP before, so I am not used to thinking in these terms). So, how would a CRTP-based solution look like?

Here is the cited question:

Is it possible to write a template function that would possess type information about the base class of the template argument? (assuming that the template argument derives from one class only)

So, I am looking for something like this:

template <class T> 
auto f(T t) -> decltype(...) { // ... is some SFINAE magic that 
                               //     catches B, the base of T
    std::cout << (B)t << std::endl;
}  

Some relevant background: I am writing a generic implementation of the A* algorithm. The template argument is a Node structure. So, the user might define:

struct NodeBase {
    REFLECTABLE((double)g, (double)f)
        // Using the REFLECTABLE macro as described here:                    
        // https://stackoverflow.com/a/11744832/2725810 
};

struct NodeData : public NodeBase {
    using Base1 = NodeBase;
    REFLECTABLE((double)F)
};

I would like to write a function that prints the contents of the node structure. REFLECTABLE does all the hard work of extracting the fields of the struct. However, when the user gives me a NodeData instance, my function needs to print the contents of the NodeBase component as well. I would like to later add overloads of my function for two and three base classes.


回答1:


to know whether a class derives from a base class we have the std::is_base_of<> template structure, which can be used in conjunction with partial specialisation, or std::enable_if.

Here is a demonstration of using a partially specialised structure to apply a an operation depending on whether it's derived from node_base or not (in this case, it just prints the base object but you could do any other operation)

#include <iostream>
#include <type_traits>

// base class
struct node_base
{

};

std::ostream& operator<<(std::ostream& os, const node_base& nb)
{
    os << "node_base_stuff";
    return os;
}

// a class derived from node_base
struct node : public node_base
{

};

// a class not derived from node_base    
struct not_node
{

};

// apply the general case - do nothing
template<class T, class = void>
struct report_impl
{
    static void apply(const T&) {};
};

// apply the case where an object T is derived from node_base    
template<class T>
struct report_impl<T, std::enable_if_t< std::is_base_of<node_base, T>::value > >
{
    static void apply(const T& t) {
        std::cout << static_cast<const node_base&>(t) << std::endl;
    };
};

// the general form of the report function defers to the partially
// specialised application class
template<class T>
void report(const T& t)
{
    report_impl<T>::apply(t);
}

using namespace std;

// a quick test    
auto main() -> int
{
    node n;
    not_node nn;
    report(n);
    report(nn);

    return 0;
}

expected output:

node_base_stuff



回答2:


Here is my own first solution. It is not CRTP though and it suffers from a huge drawback as explained at the end of the answer:

template <class Base1_ = void, class Base2_ = void, class Base3_ = void,
          class Base4_ = void>
struct ManagedNode;

// For classes that do not derive
template <> struct ManagedNode<void, void, void, void> {
    using Base1 = void; using Base2 = void; using Base3 = void;
    using Base4 = void;
};
// To avoid inaccessible base
// See http://stackoverflow.com/q/34255802/2725810
struct Inter0: public ManagedNode<>{};

// For classes that derive from a single base class
template <class Base1_>
struct ManagedNode<Base1_, void, void, void> : public Inter0,
                                               public Base1_ {
    using Base1 = Base1_;
};
// To avoid inaccessible base
template <class Base1_>
struct Inter1: public ManagedNode<Base1_>{};

// For classes that derive from two base classes
template <class Base1_, class Base2_>
struct ManagedNode<Base1_, Base2_, void, void> : public Inter1<Base1_>,
                                                 public Base2_ {
    using Base2 = Base2_;
};

// Some user classes for testing the concept

struct A : public ManagedNode<> {
    int data1;
};

struct B : public ManagedNode<> {};

struct C : public ManagedNode<A, B> {};

int main() {
    C c;
    std::cout << sizeof(c) << std::endl;
    return 0;
}

This code produces the output of 12, which means that c contains the data1 member three times! For my purposes this drawback over-weighs the benefits of the reflection that this approach provides. So, does anyone have a suggestion for a better approach?



来源:https://stackoverflow.com/questions/34223547/what-would-a-crtp-based-solution-to-this-look-like

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!