Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specialize a templated class's member struct

Say I have the following templated class:

template<typename T>
class Foo {
    struct store_t {
        uint8_t data[];
    } store;
    /// other stuff using T
}

Is there a way to construct a specialized version of the internal struct that would equate to something like this:

class Foo {
    struct store_t {
        uint16_t f1;
        uint16_t f2;
    } store;
    /// other stuff using T
}

I would prefer to keep most of the "other stuff using T" unspecialized. I would specialize some accessors though. I feel I would want to write something like

template<>
struct store_t {
    uint16_t f1;
    uint16_t f2;
} Foo<someT>::store;

but that of course doesn't work.

like image 643
Choumarin Avatar asked Jan 27 '23 09:01

Choumarin


2 Answers

As with most things in life, the answer to "how do I solve this problem I'm having with templates" is "use more templates."

Solution 1 - write store_t as a template

Thankfully, we don't have to do anything crazy. Let's write store_t outside of Foo as a template:

template<bool use_uint8>
struct Foo_store_t {
    uint8_t data[]; 
};
template<>
struct Foo_store_t<false> {
    uint16_t f1;
    uint16_t f2;
};

Now, when writing Foo, we can just pick which one we wanna use by testing some condition:

template<class T>
class Foo {
    constexpr static bool use_uint8 = /* stuff */; 
    using store_t = Foo_store_t<use_uint8>; 
    store_t store;
};

Solution 2 - write two versions of store_t, use std::conditional

This one is also pretty straight-forward. std::conditional lets you pick between two different (arbitrary) types using a boolean.

struct store_A {
    uint8_t data[];
};
struct store_B {
    uint16_t f1;
    uint16_t f2;
};
class Foo {
    constexpr static bool useVersionA = /* stuff */; 
    using store_t = std::conditional_t<useVersionA, store_A, store_B>; 
};

Here I'm using std::conditional_t, which appears in C++14, but if you're restricted to using C++11 just do:

class Foo {
    constexpr static bool useVersionA = /* stuff */; 
    using store_t = typename std::conditional<useVersionA, store_A, store_B>::type; 
};
like image 153
Alecto Irene Perez Avatar answered Feb 04 '23 13:02

Alecto Irene Perez


Just for fun, I show another possible solution based over a sort of self-inheritance.

Suppose you want specialize Foo for the type bool.

You can write the main Foo adding a template non-type parameter with a default value (say a bool with a default value of true)

template <typename T, bool = true>
struct Foo
 {
    struct store_t
     { std::uint8_t data[10]; }   store;

    /// other stuff using T
    T value;
 };

I've added T value as example of "other stuff using T".

Now you can specialize Foo<bool> inheriting from Foo<bool, false>

template <>
struct Foo<bool> : public Foo<bool, false>
 {
    struct store_t
     { std::uint16_t f1, f2; }   store;
 };

This way you can specialize store_t/store (and other members, if you want) inheriting from Foo<bool, false> the "other stuff using T" (a bool value, by example).

The following is a full compiling example

#include <cstdint>
#include <type_traits>

template <typename T, bool = true>
struct Foo
 {
    struct store_t
     { std::uint8_t data[10]; }   store;

    T value;
 };

template <>
struct Foo<bool> : public Foo<bool, false>
 {
    struct store_t
     { std::uint16_t f1, f2; }   store;

    // inherits a bool value from Foo<bool, false>
 };

int main()
 {
   Foo<int>   fi;
   Foo<bool>  fb;

   static_assert( std::is_same<decltype(fi.value),
                               int>::value, "!");
   static_assert( std::is_same<decltype(fi.store.data),
                               std::uint8_t[10]>::value, "!");
   static_assert( std::is_same<decltype(fb.value),
                               bool>::value, "!");
   static_assert( std::is_same<decltype(fb.store.f2),
                               std::uint16_t>::value, "!");
 }
like image 45
max66 Avatar answered Feb 04 '23 14:02

max66