Есть ли более простой способ добраться до члена класса, завернутого в интеллектуальный указатель?
Извините за загадочное название. Предположим, что это определение:
struct TestNode {
using CostType = double;
};
Я хотел бы иметь возможность определить шаблон класса следующим образом:
template <typename NodeP,
typename MyPolicy = /* CostType of the node type corresponding to NodeP */ >
struct TT {
};
В приведенном выше определении NodeP
может быть либо простым , либо умным указателем для класса, который определяет CostType
, например TestNode
. Проблема: как указать значение по умолчанию для параметра шаблона MyPolicy
как CostType
типа node, соответствующее NodeP
?
Вот мое решение:
// like std::remove_pointer, but works with smart pointers as well
template <typename PT> struct my_remove_pointer {
using type = typename
std::remove_reference< decltype( *((PT)nullptr) ) >::type;
};
struct TestNode {
using CostType = double;
};
template <typename NodeP,
typename MyPolicy = typename my_remove_pointer<NodeP>::type::CostType>
struct TT {
};
Существует ли более простой подход к этой проблеме? В частности, мне не хватает стандартной библиотеки, которая упростит решение?
Ответы
Ответ 1
В стандартной библиотеке есть вспомогательный класс pointer_traits
. Это похоже на то, что вы хотите.
запустить онлайн
#include <iostream>
#include <memory>
#include <typeinfo>
struct TestNode {
using CostType = double;
};
template <typename NodeP,
typename MyPolicy = typename std::pointer_traits<NodeP>::element_type::CostType>
struct TT {
typedef MyPolicy xxx;
};
int main () {
TT<TestNode*>::xxx a = 2.8;
TT<std::unique_ptr<TestNode>>::xxx b = 3.14;
std::cout << a << std::endl;
std::cout << b << std::endl;
return 0;
}
Ответ 2
В некоторых случаях я могу быть немного читабельнее
template<typename T>
using remove_pointer_t = typename my_remove_pointer<T>::type;
template<typename T>
using cost_type_t = typename remove_pointer_t<T>::CostType;
Теперь в вашем коде
template <typename NodeP,
typename MyPolicy = cost_type_t<NodeP>>
struct TT {
};
Ответ 3
Так как смарт-указатель поддерживает разыменование и .get()
, мы можем написать черту типа с помощью void_t
, чтобы получить базовый тип:
template <typename... >
using void_t = void;
// base case: not any kind of pointer
template <typename T, typename = void>
struct underlying_type {
using type = T;
};
// raw pointer
template <typename T, typename = void>
struct underlying_type<T*, void> {
using type = T;
};
// smart pointer
template <typename T>
struct underlying_type<T, void_t<
decltype(*std::declval<T>()),
decltype(std::declval<T>().get())
>>
{
using type = std::remove_reference_t<
decltype(*std::declval<T>())
>;
};
template <typename T>
using underlying_type_t = typename underlying_type<T>::type;
Если вам не нужно поддерживать не указатели (я не уверен), то вы можете просто сделать:
template <typename T>
using underlying_type_t = std::remove_reference_t<decltype(*std::declval<T>())>;
В любом случае, если у вас есть псевдоним:
template <typename NodeP,
typename MyPolicy = underlying_type_t<NodeP>::Cost>
struct TT { ... };