Enforcing a common template type parameter among two template type parameters that are themselves templates

Advertisements

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.

>Solution :

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

Leave a ReplyCancel reply