c++templatesmetaprogrammingtemplate-meta-programming

How to constrain a C++ template template argument to be a child of a templated type?


In the snippet below, base_type_t has 2 template arguments; T1 and k1. The templates foo_t, bar_t, and baz_t derive from this base, using the derived template's parameter for T1, but providing a specific value for k1.

In a function template, I want the TemplatedType argument to accept only such subclasses of base_type_t, which do already have the argument k1 specified, but the argument T1 unspecified. Does C++ allow this?

template<typename T1, int k1>
struct base_type_t {};

template<typename T1>
struct foo_t : public base_type_t<T1, 1> {};

template<typename T1>
struct bar_t : public base_type_t<T1, 1> {};

template<typename T1>
struct baz_t : public base_type_t<T1, 16> {};


// How do I restrict `TemplatedType` to be a child of `base_type_t`,
// which has the parameter `k1` already specified?
template<template<typename> typename TemplatedType>
void UseModel()
{
    // Only the template argument `T1` of `base_type_t` is specified.
    // The argument `k1` must be specified by `TemplatedType`.
    TemplatedType<float> instance1 { };
    TemplatedType<bool> instance2 { };
}

int main()
{
    UseModel<foo_t>();
    UseModel<bar_t>();
    UseModel<baz_t>();

    return 0;
}

Solution

  • Maybe you can declare a couple of function with the same name. The first one, a template one and more specific, receiving a base_type_t; the second one generic receiving a generic value

    template <int k>
    std::true_type checkBase (base_type_t<float, k> const &);
    
    std::false_type checkBase (...); 
    

    Then a little using to check if a generic type is (or derive from) a base_type_t type

    template <typename T>
    using IsBaseTypeChild = decltype(checkBase(std::declval<T>()));
    

    Now you can use SFINAE to enable UseModel() only if TemplatedType<int> can call the specialized version of checkBase().

    template<template<typename> typename TemplatedType>
    std::enable_if_t<IsBaseTypeChild<TemplatedType<float>>::value> UseModel()
    {
        [[maybe_unused]] TemplatedType<float> instance1 { };
        [[maybe_unused]] TemplatedType<bool> instance2 { };
    }
    

    The following is a full compiling example. Observe that I've added a call to a bad_t type that can't call UseModel()

    #include <type_traits>
    
    template<typename T1, int k1>
    struct base_type_t {};
    
    template<typename T1>
    struct foo_t : public base_type_t<T1, 1> {};
    
    template<typename T1>
    struct bar_t : public base_type_t<T1, 1> {};
    
    template<typename T1>
    struct baz_t : public base_type_t<T1, 16> {};
    
    template<typename T1>
    struct bad_t {};
    
    template <int k>
    std::true_type checkBase (base_type_t<float, k> const &);
    
    std::false_type checkBase (...);
    
    template <typename T>
    using IsBaseTypeChild = decltype(checkBase(std::declval<T>()));
    
    template<template<typename> typename TemplatedType>
    std::enable_if_t<IsBaseTypeChild<TemplatedType<float>>::value> UseModel()
    {
        [[maybe_unused]] TemplatedType<float> instance1 { };
        [[maybe_unused]] TemplatedType<bool> instance2 { };
    }
    
    int main()
    {
        UseModel<foo_t>();
        UseModel<bar_t>();
        UseModel<baz_t>();
    
        // UseModel<bad_t>(); // <--- compilation error if you enable this line:
                              //       bad_t doesn't derive from base_type_t
    
        return 0;
    }