Разрешение типа элемента как по указателям, так и по классам
Target. Наша цель - написать шаблон using
, который принимает тип с разыменованием в качестве входных данных и возвращает элементтип.
template<class T>
using element_type_t = /* stuff */;
Метод. Мы можем использовать SFINAE, чтобы проверить, есть ли свойство element_type
, а если нет, мы вернемся к использованию std::remove_reference<decltype(*P())>()
.
// This is fine to use in an unevaluated context
template<class T>
T& reference_to();
// This one is the preferred one
template<class Container>
auto element_type(int)
-> typename Container::element_type;
// This one is the fallback if the preferred one doesn't work
template<class Container>
auto element_type(short)
-> typename std::remove_reference<decltype(*reference_to<Container>())>::type;
Получив эту функцию, мы можем написать element_type_t
, просто получив тип возвращаемого значения element_type
.
// We alias the return type
template<class T>
using element_type_t = decltype(element_type<T>(0));
Почему мы не всегда можем получить element_type, разыменовав его? Если вы попытаетесь всегда получить тип значения с помощью оператора *
, это может вызвать проблемы с вещамикак итератор для std::vector<bool>
, который возвращает объект, который действует как bool, но инкапсулирует битовые манипуляции.В этих случаях тип элемента отличается от типа, возвращаемого разыменованием его.
Определение, принимает ли конструктор указатель или значение
Причина, по которой ваш код не работает с std::optional
, заключается в том, что конструктор std::optional
принимает само значение, а не указатель назначение.Чтобы определить, какой конструктор нам нужен, мы снова используем SFINAE для определения.
// Base case - use new operator
template<class Container>
auto make_new_impl(int)
-> decltype(Container{new element_type_t<Container>})
{
return Container{new element_type_t<Container>};
}
// Fallback case, where Container takes a value
template<class Container>
auto make_new_impl(long)
-> decltype(Container{element_type_t<Container>()})
{
return Container{element_type_t<Container>()};
}
Теперь мы можем написать make_new
, чтобы он вызывал make_new_impl
:
template<class Container>
auto make_new() {
return make_new_impl<Container>(0);
}
Пример. Теперь мы можем использовать make_new
длясделать либо std::optional
, std::shared_ptr
, либо даже обычный указатель.
#include <optional>
#include <memory>
int main() {
// This works
int* ptr = make_new<int*>();
// This works too
std::shared_ptr<int> s = make_new<std::shared_ptr<int>>();
// This also works
std::optional<int> o = make_new<std::optional<int>>();
}