I'm trying to create a class that can contain a type pack.
// Pack.hpp
template <typename... Types>
class Pack final
{
Pack(const std::tuple<Types...> items) : items_(std::move(items)) {};
std::tuple<Types...> items_;
};
But I would like Pack
to expose some methods based on the Types
inside of it. For example, I want to do this:
// Consumer.cpp
Person person{ ... };
Car car{ ... };
Pack<Person, Car> personAndCarPack{ { person, car } };
Pack<Person> personPack{ { person } };
// Ok
personAndCarPack.getCar();
// Ok
personAndCarPack.getPerson();
// Ok
personPack.getPerson();
// Shouldn't compile - getCar() shouldn't exist!
personPack.getCar();
I'm assuming I have to declare it more or less this way:
// Pack.hpp
template <typename... Types>
class Pack final
{
Pack(const std::tuple<Types...> items) : items_(std::move(items)) {};
std::tuple<Types...> items_;
Car getCar()
{
return std::get<Car>(items_);
}
Person getPerson()
{
return std::get<Person>(items_);
}
};
But of course that exposes the methods in Pack
regardless of the types.
The closest I've got is this:
typename std::enable_if<(std::is_same_v<Types, Car> && ...), Car>::type
getCar()
{
return std::get<Car>(items_);
}
But it doesn't compile; I get
'enable_if' cannot be used to disable this declaration
in the std::is_same_v()
call.
I am probably misunderstanding how it all works. I've found some answers that are somewhat related, but none with the right combination of factors according to what I need.
Is it possible to implement this functionality at all? If so, how?
Is it possible to implement this functionality at all? If so, how?
Yes, it is possible. The minimal change ᕯsolution is without std::enable_if
/ SFINAE.
// Method to get a Car from the Pack (enabled only if Car is in Types...)
template <typename T = Car>
constexpr auto getCar() -> decltype(std::get<Car>(items_))
{
return std::get<Car>(items_);
}
// Method to get a Person from the Pack (enabled only if Person is in Types...)
template <typename T = Person>
constexpr auto getPerson() -> decltype(std::get<Person>(items_))
{
return std::get<Person>(items_);
}
ᕯ Keep in mind that the above does not disable the function from usage in unevaluated context (i.e. decltype(personPack.getCar()) x = personAndCarPack.getCar();
will still be valid).
Alternatively, with std::enable_if_t
and std::disjunction
, you might achieve the same effect as well.
// Method to get a Car from the Pack (enabled only if Car is in Types...)
template <typename T = Car>
constexpr auto getCar()
-> std::enable_if_t<std::disjunction<std::is_same<Types, T>...>::value, T>
{
static_assert(std::is_same_v<T, Car>, "T must be of type Car!");
return std::get<T>(items_);
}
// Method to get a Person from the Pack (enabled only if Person is in Types...)
template <typename T = Person>
constexpr auto getPerson()
-> std::enable_if_t<std::disjunction<std::is_same<Types, T>...>::value, T>
{
static_assert(std::is_same_v<T, Person>, "T must be of type Person!");
return std::get<T>(items_);
}
(For future readers) Since c++20, however, the solution will be much simpler with requires
constraints
// Method to get a Car from the Pack (enabled only if Car is in Types...)
constexpr auto getCar() requires (std::is_same_v<Types, Car> || ...)
{
return std::get<Car>(items_);
}
// Method to get a Person from the Pack (enabled only if Person is in Types...)
constexpr auto getPerson() requires (std::is_same_v<Types, Person> || ...)
{
return std::get<Person>(items_);
}