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.

2

There are 2 best solutions below

2
On BEST ANSWER

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;
}
0
On

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.