I have a templated class that contains a tuple.
template <class... SomeTypes>
class Foo
{ ... };
How can I apply a constraint on the types contained in the SomeTypes parameter pack?
For instance, if I wanted to ensure that all of the types fit the std::movable
concept?
What if SomeTypes
was also templates?
template < template<typename> class... SomeTypes>
class Bar
{ ... };
Here's a more specific case of what I'm looking for. The code shouldn't (and doesn't) compile because FooB
is non-copyable.
Unfortunately the compiler doesn't give the best error messages when it runs into this (in the actual code I'm dealing with the types are more complex and this ends up with giant error novels). I'm hoping I could add some sort of requires
clause to FooContainer
which would give a much clearer error.
#include <tuple>
#include <concepts>
template <typename ContainerType>
class Foo
{
};
template <typename ContainerType>
class FooA : public Foo<ContainerType>
{
public:
int some_data {0};
void DoSomething()
{
some_data++;
}
};
template <typename ContainerType>
class FooB : public Foo<ContainerType>
{
public:
// non-copyable object
FooB() = default;
FooB( const FooB& ) = delete;
FooB& operator=( const FooB& ) = delete;
~FooB() = default;
int some_data {0};
void DoSomething()
{
some_data++;
}
};
template < template<typename> class... SomeTypes>
// requires (std::copyable<SomeTypes...>) ???
class FooContainer
{
public:
std::tuple<SomeTypes<FooContainer>...> my_tuple;
template <std::size_t I = 0, std::enable_if_t<I == sizeof...(SomeTypes)>* = nullptr>
void DoAllThings()
{
}
template <std::size_t I = 0, std::enable_if_t<I < sizeof...(SomeTypes)>* = nullptr>
void DoAllThings()
{
std::get<I>(my_tuple).DoSomething();
DoAllThings<I + 1>();
}
};
using MyContainer = FooContainer<FooA, FooB>;
int main()
{
MyContainer my_bar;
my_bar.DoAllThings();
MyContainer my_bar2 = my_bar;
}
--Edit 2--
So after playing around with some of the suggestions here, the closest I've come up with to what I wanted is to use an external static function to do the check.
template <typename FooType>
requires (std::copyable<FooType>)
constexpr bool FooTypeCheck()
{
return true;
}
static_assert(FooTypeCheck<FooA<int>>());
This gives me the check I want (is the derived Foo class copyable), and therefor the easier to read compiler output if the check fails. For the purpose of the type check, the ContainerType doesn't matter, so I just pass an int. Unfortunately, it also means I need to add the static_assert for each derived Foo class, and the int I pass for ContainerType is a little odd.
--Final Edit (Answer)-- I've given credit to @Ted Lyngmo for his answer as it was close to what I needed. Since the ContainerType parameter is just part of the CRTP, it doesn't really matter for the purposes of the constraint, and I'm able to just use int, and the solution is a little simpler.
template <template <typename> class FooType>
concept FooTypeCheck = std::copyable<FooType<int>>;
template < template<typename> class... SomeTypes>
requires (... && FooTypeCheck<SomeTypes>)
class FooContainer
{ ... };
The most straight forward way would be to use the std::movable
concept:
#include <concepts>
template <std::movable... SomeTypes>
class Foo {
// ...
};
What if SomeTypes was also templates?
Class templates can be specialized and you can't check if a class template is movable for all types. You need actual instances of the class templates to be able to check for movability.
For example, if you want to check that the supplied class templates becomes movable when instantiated with int
and std::string
, you could check that like this:
template<template <class> class T, class... Ts>
concept MovableWith = (... && std::movable<T<Ts>>);
template <template <class> class... SomeTypes>
requires (... && MovableWith<SomeTypes, int, std::string>)
class Bar {
// ...
};