1

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.

Community
  • 1
  • 1
AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68
  • You seem to be assuming you can constrain the client classes of your feature to using a special syntax to declare their member variables. But then you don't seem to be open to constraining them to using a special syntax to declare their base class. I don't know all the details of your requirements, but it seems plausible that CRTP could be part of the special syntax you force client classes to use to declare their base classes. `struct NodeData : public baseHolder` – JSF Dec 11 '15 at 12:35

2 Answers2

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
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • This assumes that I know the list of possible base classes, which is not the case -- the whole class hierarchy comes from the user. – AlwaysLearning Dec 12 '15 at 16:41
  • Hmm. If the design of this algorithm needs reflection, there's probably a more elegant way to express the solution. There's a good reason that reflection has been left out of the standard - it's unnecessary. – Richard Hodges Dec 12 '15 at 17:41
0

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?

AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68