c++templatestype-parametertemplate-templates

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


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