Search code examples
c++c++11staticvariadic-templatesstdtuple

How to group different static classes?


I have a templated static class with different specializations, like this:

template<typename Parameter >
class MyClass
{};

 template<>
class MyClass<Parameter1>
{
public:
     static constexpr Integer myarray[]={0};
     static constexpr Integer myarraysize=1;    
 };

  template<>
class MyClass<Parameter2>
{
public:
     static constexpr Integer myarray[]={0,1};
     static constexpr Integer myarraysize=2;    
 };

Now I would like to group somehow these informations in a new class

template<typename MyClass1, typename MyClasses... >
class MyGroupClass{
//do something...}

where I could give as variadic template parameters the different classes and then I could access the different static methods.

For example, I would like to to something as MyGroupClass[n]::myarraysize to access myarraysize related to n-th MyClass.

I imagine I could create a tuple (so having std::get<n>()), but it is not very clear to me how to do this, since I have no constructors for such single static classes. After all, the classes are static.

Is it possible to achieve what I want? If so, could you please enlighten me? Thank you.


Solution

  • I would like to to something as MyGroupClass[n]:: myarraysize to access myarraysize related to n-th MyClass. I imagine I could create a tuple (so having std::get()),

    It seems to me that you have to distinguish two cases.

    (1) when the myarraysize in the different classes are of different types you can create a std::tuple of the different sizes an use std::get() to extract the values.

    By example

    template <typename ... Ts>
    struct MyGroupStruct
     {
       const std::tuple<decltype(Ts::myarraysize)...> tpl { Ts::myarraysize... };
    
       template <std::size_t N>
       auto get () const -> decltype(std::get<N>(tpl))
        { return std::get<N>(tpl); }
     };
    

    Starting from C++14 you can avoid the trailing return type for get() and write simply

       template <std::size_t N>
       auto get () const
        { return std::get<N>(tpl); }
    

    Observe that the MyGroupStruct::get() receive the index (N) as template parameter. So it needs a compile time known value. This is necessary because the type returned from the template method differ depending from the index so must be known compile time.

    (2) when all myarraysize in different classes are of the same type, you can also create a std::array of that type; something as

    template <typename ... Ts>
    struct MyGroupStruct
     {
       using myType = typename std::tuple_element<0u, 
          std::tuple<decltype(Ts::myarraysize)...>>::type;
    
       const std::array<myType, sizeof...(Ts)> arr {{ Ts::myarraysize... }};
    
       myType & get (std::size_t n)
        { return arr[n]; }
     };
    

    Observe that, in this case, the return value for get() is ever the same so it can receive a run-time index as (not template) parameter.

    The following is a full compiling example for the different-types (tuple based) case

    #include <tuple>
    #include <string>
    #include <iostream>
    
    struct Par1 {};
    struct Par2 {};
    struct Par3 {};
    struct Par4 {};
    
    template <typename>
    struct MyStruct;
    
    template <>
    struct MyStruct<Par1>
     { static constexpr int myarraysize {1}; };
    
    constexpr int MyStruct<Par1>::myarraysize;
    
    template <>
    struct MyStruct<Par2>
     { static constexpr long myarraysize {2l}; };
    
    constexpr long MyStruct<Par2>::myarraysize;
    
    template <>
    struct MyStruct<Par3>
     { static constexpr long long myarraysize {3ll}; };
    
    constexpr long long MyStruct<Par3>::myarraysize;
    
    template <>
    struct MyStruct<Par4>
     { static const std::string myarraysize; };
    
    const std::string MyStruct<Par4>::myarraysize {"four"};
    
    template <typename ... Ts>
    struct MyGroupStruct
     {
       const std::tuple<decltype(Ts::myarraysize)...> tpl { Ts::myarraysize... };
    
       template <std::size_t N>
       auto get () const -> decltype(std::get<N>(tpl))
        { return std::get<N>(tpl); }
     };
    
    int main ()
     {
       MyGroupStruct<MyStruct<Par1>, MyStruct<Par2>,
                     MyStruct<Par3>, MyStruct<Par4>> mgs;
    
       std::cout << mgs.get<0>() << std::endl;
       std::cout << mgs.get<1>() << std::endl;
       std::cout << mgs.get<2>() << std::endl;
       std::cout << mgs.get<3>() << std::endl;
    
       static_assert( std::is_same<int const &,
                                   decltype(mgs.get<0>())>::value, "!" );
       static_assert( std::is_same<long const &,
                                   decltype(mgs.get<1>())>::value, "!" );
       static_assert( std::is_same<long long const &,
                                   decltype(mgs.get<2>())>::value, "!" );
       static_assert( std::is_same<std::string const &,
                                   decltype(mgs.get<3>())>::value, "!" );
     }
    

    And now a full compiling example for the equal-types (array based) case

    #include <tuple>
    #include <array>
    #include <string>
    #include <iostream>
    
    struct Par1 {};
    struct Par2 {};
    struct Par3 {};
    struct Par4 {};
    
    template <typename>
    struct MyStruct;
    
    template <>
    struct MyStruct<Par1>
     { static constexpr int myarraysize {1}; };
    
    constexpr int MyStruct<Par1>::myarraysize;
    
    template <>
    struct MyStruct<Par2>
     { static constexpr int myarraysize {2}; };
    
    constexpr int MyStruct<Par2>::myarraysize;
    
    template <>
    struct MyStruct<Par3>
     { static constexpr int myarraysize {3}; };
    
    constexpr int MyStruct<Par3>::myarraysize;
    
    template <>
    struct MyStruct<Par4>
     { static const int myarraysize {4}; };
    
    const int MyStruct<Par4>::myarraysize;
    
    template <typename ... Ts>
    struct MyGroupStruct
     {
       using myType = typename std::tuple_element<0u, 
             std::tuple<decltype(Ts::myarraysize)...>>::type;
    
       const std::array<myType, sizeof...(Ts)> arr {{ Ts::myarraysize... }};
    
       myType & get (std::size_t n)
        { return arr[n]; }
     };
    
    int main ()
     {
       MyGroupStruct<MyStruct<Par1>, MyStruct<Par2>,
                     MyStruct<Par3>, MyStruct<Par4>> mgs;
    
       std::cout << mgs.get(0) << std::endl;
       std::cout << mgs.get(1) << std::endl;
       std::cout << mgs.get(2) << std::endl;
       std::cout << mgs.get(3) << std::endl;
    
       static_assert( std::is_same<int const &,
                                   decltype(mgs.get(0))>::value, "!" );
     }
    

    -- EDIT --

    The OP asks

    how to change the code if I want to access also myarray and not only myarraysize?

    With a C-style array is a little more complicated because you can't initialize a tuple of C-style arrays with C-style arrays.

    I propose you to use a tuple of references to C-style arrays.

    So, given some MyClasses with only myarray (why add the size when you can deduce it?)

    template <typename>
    struct MyStruct;
    
    template <>
    struct MyStruct<Par1>
     { static constexpr int myarray[] {0}; };
    
    constexpr int MyStruct<Par1>::myarray[];
    
    // other MyStruct specializations ...
    

    you can add a tuple of references (tuple, not std::array, because int[1], int[2], int[3] and int[4] are all different types) and a std::array<std::size_t, sizeof...(Ts)> for sizes.

    I mean... you can write something as follows

    template <typename ... Ts>
    struct MyGroupStruct
     {
       std::tuple<decltype(Ts::myarray) & ...> const tpl { Ts::myarray... };
    
       std::array<std::size_t, sizeof...(Ts)> const arr
        {{ sizeof(Ts::myarray)/sizeof(Ts::myarray[0])... }};
    
       template <std::size_t N>
       auto getArr () const -> decltype(std::get<N>(tpl))
        { return std::get<N>(tpl); }
    
       std::size_t getSize (std::size_t n) const
        { return arr[n]; }
     };
    

    what does this stand for "const -> decltype(std::get(tpl))"?

    const is unrelated to decltype().

    const, after the list of method parameters, say that the method can be used also by constant object because doesn't change member variables.

    Regarding decltype() look for "trailing return type" for more informations.

    In short, for C++11, the idea is that in

    auto foo () -> decltype(something)
     { return something; }
    

    auto say "look after -> for the return type" and decltype(something) is "the type of something"

    You can also write

    decltype(something) foo ()
     { return something; }
    

    if something is known before the list of function arguments, but the auto/-> decltype(something) form become useful when something contains template parameters

    By example

    template <typename T1, typename T2>
    auto sum (T1 const & t1, T2 const & t2) -> decltype(t1+t2)
     { return t1+t2; }
    

    Starting from C++14 the "trailing return type" is less used because you can simply write

    template <typename T1, typename T2>
    auto sum (T1 const & t1, T2 const & t2)
     { return t1+t2; }
    

    because auto say to the compiler "deduce the return type from the return expression" (so from t1+t2 in this case.

    This avoid a lot of redundancies.

    And can we use "auto" also in C++11?

    auto as return type? Without trailing return type?

    Unfortunately is available only starting from C++14.

    Follows another full example with myarray and deduced sizes.

    #include <tuple>
    #include <array>
    #include <string>
    #include <iostream>
    
    struct Par1 {};
    struct Par2 {};
    struct Par3 {};
    struct Par4 {};
    
    template <typename>
    struct MyStruct;
    
    template <>
    struct MyStruct<Par1>
     { static constexpr int myarray[] {0}; };
    
    constexpr int MyStruct<Par1>::myarray[];
    
    template <>
    struct MyStruct<Par2>
     { static constexpr int myarray[] {0, 1}; };
    
    constexpr int MyStruct<Par2>::myarray[];
    
    template <>
    struct MyStruct<Par3>
     { static constexpr int myarray[] {0, 1, 2}; };
    
    constexpr int MyStruct<Par3>::myarray[];
    
    template <>
    struct MyStruct<Par4>
     { static constexpr int myarray[] {0, 1, 2, 3}; };
    
    constexpr int MyStruct<Par4>::myarray[];
    
    template <typename ... Ts>
    struct MyGroupStruct
     {
       std::tuple<decltype(Ts::myarray) & ...> const tpl { Ts::myarray... };
    
       std::array<std::size_t, sizeof...(Ts)> const arr
        {{ sizeof(Ts::myarray)/sizeof(Ts::myarray[0])... }};
    
       template <std::size_t N>
       auto getArr () const -> decltype(std::get<N>(tpl))
        { return std::get<N>(tpl); }
    
       std::size_t getSize (std::size_t n) const
        { return arr[n]; }
     };
    
    int main ()
     {
       MyGroupStruct<MyStruct<Par1>, MyStruct<Par2>,
                     MyStruct<Par3>, MyStruct<Par4>> mgs;
    
       std::cout << mgs.getSize(0) << std::endl;
       std::cout << mgs.getSize(1) << std::endl;
       std::cout << mgs.getSize(2) << std::endl;
       std::cout << mgs.getSize(3) << std::endl;
    
       static_assert( std::is_same<std::size_t,
                                   decltype(mgs.getSize(0))>::value, "!" );
    
       std::cout << mgs.getArr<0>()[0] << std::endl;
       std::cout << mgs.getArr<1>()[1] << std::endl;
       std::cout << mgs.getArr<2>()[2] << std::endl;
       std::cout << mgs.getArr<3>()[3] << std::endl;
    
       static_assert( std::is_same<int const (&)[1],
                                   decltype(mgs.getArr<0>())>::value, "!" );
       static_assert( std::is_same<int const (&)[2],
                                   decltype(mgs.getArr<1>())>::value, "!" );
       static_assert( std::is_same<int const (&)[3],
                                   decltype(mgs.getArr<2>())>::value, "!" );
       static_assert( std::is_same<int const (&)[4],
                                   decltype(mgs.getArr<3>())>::value, "!" );    
     }