How do I best implement a Derived with members depending on a Template Parameter Pack Specialization?

42 views Asked by At

Goal

I want to implement a struct Derived<size_t ...Tpar>, satisfying certain requirements. I have a functioning solution code, but it has certain disadvantages. I seek a better way of doing it.

Requirements

Derived<size_t ...Tpar> must inherit exclusively from Base<size_t ...Tpar>. The relevant cases for me are Derived<size_t> and Derived<>.

Members and attributes:

  • an identical member function void a(void); for empty and non-empty Tpar
  • an array data of length Tpar[0] for Derived<Tpar...> for non-empty Tpar only
  • and a function void b(void); for non-empty Tpar only, that performs computations on data.

Minimum working example

As the comments in the example code below indicate, these issues make the solution unappealing:

  1. Since Derived<> and Derived<size_t> both implement a(), I thought of inheriting Derived<size_t> from Derived<>. But this does not work because Derived<size_t> would inherit from Base<>, which is against requirement.
  2. I hence opted for a specialized Submodule. But this does not work because Derived is already a template, hence I cannot specialize a submodule template within its namespace as per the rules of the language.
  3. I thus make the Submodule in two non-template explicit variants and employ conditional_t to obtain the right type of submodule in either Derived specialization. 3.1. Is there a better alternative for the array (that I have to augment with a zero only for the conditional to not complain that aTpar[0] is a null-pointer? 3.2. Is there a way for templating the submodule or making the entire code shorter, without exposing the Submodule to the compilation unit?
#include<iostream>
#include<array>
#include<type_traits>

template<size_t ...Tpar> class Base{};

// I cannot inherit Derived<Tpar> from Derived<> since this would make Derived<Tpar> a derived of Base<>.
template<size_t ...Tpar>
class Derived: Base<Tpar...>{
    // c++ won't allow template structure specializations within a template struct
    template<size_t n>
    struct SubModule_paged{
        double data[n];
        void foo(){
            // foo is a routine that computes on data.
            std::cout << "Sub<"<<n<<">foo.\n";
        }
    };
    struct SubModule_empty{
        // no data, hence no foo.
        void foo() = delete; // COMPILER HINT: Intentionally, Derived<> has no member b.
    };
    static constexpr size_t nTpar = sizeof...(Tpar);
    static constexpr std::array<const size_t, nTpar+1> aTpar = {Tpar...,0}; // <-- This is ugly!
    using SubModuleType = std::conditional_t< (nTpar>0) , SubModule_paged<aTpar[0]> , SubModule_empty >;
    //
    SubModuleType sub;
    //
public:
    void a(){
        std::cout << "a.\n";
    }
    void b(){
        std::cout << "b.\n";
        sub.foo();
    }
};

int main(){
    Derived< > x;
    Derived<3> y;
    
    x.a();
    //x.b();
    
    y.a();
    y.b();
}
1

There are 1 answers

4
463035818_is_not_an_ai On BEST ANSWER

an identical member function void a(void); for empty and non-empty Tpar

Ok, a should be member of Base.

an array data of length Tpar[0] for Derived<Tpar...> for non-empty Tpar only and a function void b(void); for non-empty Tpar only, that performs computations on data.

Ok, specialize Derived for when Tpar... is at least one argument.

#include <iostream>
#include <array>

template<size_t ...Tpar> struct Base{
    void a(void) {}
};

// base template
template <size_t ...Tpar> struct Derived : public Base<Tpar...> {};

// specialization for one or more size_t 
template <size_t Tfirst,size_t ...Tpar> struct Derived<Tfirst,Tpar...> : public Base<Tfirst,Tpar...> {
    std::array<size_t,Tfirst> arr;
    void b() {}
};

int main() {
    Derived<> d;
    //d.b(); error
    //d.arr; error
    Derived<42> e;
    e.b();
    e.arr;
}

Maybe you will not agree about making a a member of Base, but the thing about distinguising between empty and non empty Tpar... is actually rather simple. You only need to consider that size_t Tfirst,size_t ...Tpar is one or more argument, while size_t Tpar... is zero or more. I think you got confused by thinking about the parameter pack like an array where you need to pick the first element via Tpar[0]. But a parameter pack is not an array and no array is needed to pick the first element either.