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.
CodePudding user response:
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;
}
CodePudding user response:
You can do this with a partial specialization that matches the requirement.
Use a primary class that takes 2 type parameters. Don't define it, so you'll get an error if you try to use it:
template <typename, typename>
struct CommonTemplateTypeParameterProducer;
Then write a partial specialization that takes 2 template parameters A
and B
, and 1 type parameter T
. If the types used in the declaration of the variable match the pattern A<T>
and B<T>
this specialization will be chosen:
template <template <typename> typename A,
template <typename> typename B,
typename T>
struct CommonTemplateTypeParameterProducer<A<T>, B<T>>
{
T Produce() { return T(); }
};
Here's a demo.