2015-12-11 7 views
1

Я задал следующий вопрос в сообщении this (приклеенный ниже для удобства). В одном из комментариев было высказано мнение о том, что для решения проблемы существует CRTP-решение. Я не могу понять, как CRTP здесь имеет значение (ну, я никогда раньше не использовал CRTP, поэтому я не привык думать в этих терминах). Итак, как будет выглядеть CRTP-решение?Каким будет решение на основе CRTP?

Вот цитируемый вопрос:

Можно ли написать шаблонную функцию, которая обладала бы информацией о типе о базовом классе аргумента шаблона? (При условии, что аргумент шаблона происходит только из одного класса)

Итак, я ищу что-то вроде этого:

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; 
} 

Некоторые соответствующие справочные материалы: Я пишу родовое реализацию алгоритма A*. Аргумент шаблона - это структура Node. Таким образом, пользователь может определить:

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) 
}; 

Я хотел бы написать функцию, которая печатает содержимое структуры узла. REFLECTABLE выполняет всю тяжелую работу по извлечению полей struct. Однако, когда пользователь дает мне экземпляр NodeData, моя функция также должна печатать содержимое компонента NodeBase. Я хотел бы позже добавить перегрузки моей функции для двух и трех базовых классов.

+0

Предполагается, что вы можете ограничить клиентские классы вашей функции специальным синтаксисом для объявления своих переменных-членов. Но тогда вы, кажется, не открыты, чтобы ограничить их использованием специального синтаксиса для объявления своего базового класса. Я не знаю всех подробностей ваших требований, но кажется правдоподобным, что CRTP может быть частью специального синтаксиса, который вы вынуждаете классы клиентов использовать для объявления своих базовых классов. 'struct NodeData: public baseHolder ' – JSF

ответ

1

Чтобы узнать, имеет ли класс производный класс, мы имеем структуру шаблона std :: is_base_of <>, которая может использоваться совместно с частичной специализацией или std :: enable_if.

Вот демонстрация использования частично специализированную структуры применить операцию в зависимости от того, является ли оно производным от node_base или нет (в этом случае, он просто печатает базовый объект, но вы могли бы сделать любую другую операцию)

#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; 
} 

ожидается выход:

node_base_stuff 
+0

Это предполагает, что я знаю список возможных базовых классов, что не так - вся иерархия классов поступает от пользователя. – AlwaysLearning

+0

Хм. Если дизайн этого алгоритма нуждается в отражении, возможно, более элегантный способ выразить это решение.Есть веская причина, что размышления остались в стороне от стандарта - это не нужно. –

0

Вот мое первое решение. Не CRTP, хотя и страдает от огромного недостатка, как описано в конце ответа:

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; 
} 

Этот код производит выход из 12, что означает, что c содержит data1 члена в три раза! Для моих целей этот недостаток перевесит преимущества отражения, которое дает этот подход. Итак, есть ли у кого-нибудь предложение для лучшего подхода?