In C++, is there any way to ensure that two or more template type parameters are themselves template types with a common template type parameter?
Let's say that I have this:
struct ArbitraryType {};
template <typename T>
class ArbitraryTemplateClass1 {
/* ... */
};
template <typename T>
class ArbitraryTemplateClass2 {
/* ... */
};
And I want a template that can take ArbitraryTemplateClass1
and ArbitraryTemplateClass2
as template type parameters, but ensure they both have a common template type parameter T
(and then do something with the type T
). For example, I'd like to be able to do something like:
template <typename U <typename T>, typename V<T>> // desired (but incorrect) syntax
struct CommonTemplateTypeParameterProducer {
T Produce() { return T(); }
};
// Which would allow this:
int main() {
CommonTemplateTypeParameterProducer<ArbitraryTemplateClass1<ArbitraryType>,
ArbitraryTemplateClass2<ArbitraryType>> producer;
// Type of t should be ArbitraryType
auto t = producer.Produce();
// This should fail to instantiate CommonTemplateTypeParameterProducer because U and V don't share a common T
//CommonTemplateTypeParameterProducer<ArbitraryTemplateClass1<ArbitraryType>,
// ArbitraryTemplateClass2<int>> producer2;
}
Are there any mechanisms that allow this kind of behavior without having direct knowledge of the arbitrary template classes and the arbitrary type? (something like adding a typedef to ArbitraryTemplateClass1
that specifies the type of T
would not be an option). If possible, I would also like to avoid adding a third template type parameter to CommonTemplateTypeParameterProducer;
ideally I would like the template to be able to deduce the common type that it is enforcing.
Note: Something like this would also help with being able to ensure that two template type parameters that are also variadic templates both have identical template arguments.
I have looked into C++ concepts/constraints/requirements and template template parameters, but so far none seem to offer solutions to this particular problem.
It's possible, but might not be the best design, because if somebody will need to add another template parameter to ArbitraryTemplateClass1
, everything will break. Or if you decide to make a version of it that's not templated, and only works for one specific type.
It's better to add something like using type = T;
to ArbitraryTemplateClass1
/2
, and check that in your template instead of the actual template parameter. CommonTemplateTypeParameterProducer
should have no business policing the template arguments of its template arguments.
#include <concepts>
template <typename T>
struct A
{
using type = T;
};
template <typename T>
struct B
{
using type = T;
};
template <typename X, typename Y>
requires std::same_as<typename X::type, typename Y::type>
struct Foo
{
X x;
Y y;
};
int main()
{
Foo<A<int>, B<int>> foo;
}
Here's another option. It has the same issue as your proposed design, but at least you don't need to spell the template argument twice:
template <typename T>
struct A {};
template <typename T>
struct B {};
template <
template <typename> typename X,
template <typename> typename Y,
typename T
>
struct Foo
{
X<T> x;
Y<T> y;
};
int main()
{
Foo<A, B, int> foo;
}
And lastly, here's exactly what you asked for. I added a helper template to extract the template argument from arbitrary templates. This, again, has the issue I described above, and also requires spelling the template argument twice.
#include <concepts>
template <typename T>
struct GetTemplateArgument {};
template <template <typename> typename T, typename U>
struct GetTemplateArgument<T<U>> {using type = U;};
template <typename T>
struct A {};
template <typename T>
struct B {};
template <typename X, typename Y>
requires std::same_as<typename GetTemplateArgument<X>::type, typename GetTemplateArgument<Y>::type>
struct Foo
{
X x;
Y y;
};
int main()
{
Foo<A<int>, B<int>> foo;
}