4

i want to create a Pool with/without thread-safe. I dont want to define a mutex field, if the pool is not thread-safe so i used std::conditional, however because it's not doing exactly what i want, and creates two "type" options, i chose "int8 (char)" as passivised mutex type. (Instead i want the whole definition is gone)

template<typename T, bool threadSafe = true>
class Pool
{
private:
    //Mutex mutex; this is the field i want it to be DISAPPEARED, i modified it as below
    std::conditional<threadSafe, Mutex, int8>::type mutex;
protected:
    static constexpr item_type_size_datatype TypeSizeX = sizeof(T) + sizeof(size_t);
public:
    Pool(size_t clusterItemCount) : ClusterItemCount(clusterItemCount),
        ClusterByteSize(clusterItemCount* TypeSizeX)
    {
#ifdef CriticalSection
        if constexpr (threadSafe)
            InitializeCriticalSection(&mutex);
#endif
    }
    ~Pool()
    {
        Clear();

#ifdef CriticalSection
        if constexpr (threadSafe)
            DeleteCriticalSection(&mutex);
#endif
    }

    T* Occupy(bool& outFirstTime)
    {
        if constexpr (threadSafe)
        {
            MutexLock(mutex);
        }

        //do the occupation

        if constexpr (threadSafe)
        {
            MutexUnlock(mutex);
        }

        return result;
    }
};

as you can see, inside methods i used "constexpr if" that works like a charm because it disables whole code blocks.

Main Question: Is there a better way to disable whole definition such as "Mutex mutex;" other than "std::conditional"

Additional Question: I am getting "uninitialized variable" warning for "int8 mutex", i have to initialized with "0".. how can i do this at compile-time with "std::conditional" manner.

Ibrahim Ozdemir
  • 613
  • 1
  • 5
  • 18

3 Answers3

5

This can be achieved via template specialization, for example:

template<bool threadSafe>
class PoolBase;

template<>
class PoolBase<false>
{// empty
};

template<>
class PoolBase<true>
{
   protected: Mutex m_mutex;
};

template<typename T, bool threadSafe = true>
class Pool: private PoolBase<threadSafe>
{
...

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • 3
    Additionally, if it is impractical to specialise the whole class, one can wrap the "maybe present, maybe not" member in a templated struct specialised in this way. – saxbophone Mar 21 '23 at 13:26
  • 1
    This solution combines two idioms: **base from member** & **empty base optimization**. – Red.Wave Mar 21 '23 at 14:43
  • that seems like a realy good solution, however compiler creates an error when i want to acces the [mutex] from [Pool] class, error: "error C2065: 'mutex': undeclared identifier". its almost like a compile time issue, and doesnt see the base template [mutex] field and says it doesnt exist – Ibrahim Ozdemir Mar 21 '23 at 15:32
  • @IbrahimOzdemir you need to access it as `this->m_mutex` or as `PoolBase::m_mutex` because it is a dependent (on template parameters) name. see also https://stackoverflow.com/questions/610245/where-and-why-do-i-have-to-put-the-template-and-typename-keywords – user7860670 Mar 21 '23 at 15:33
  • @user7860670 hey thanks man that worked.. overall this solution is great, doesnt have a virtual method (vtable), light effective thanks a lot – Ibrahim Ozdemir Mar 21 '23 at 15:40
0

As the other answer says, you can use template specialisation for this, as unfortunately it's not possible to use std::enable_if for a member declaration.

To make it so you don't need to specialise the entire class and thus duplicate definitions and declarations, you can create a container struct which is template-specialised depending on whether you want to include the variable or not, then use this templated struct as a member:

template <bool include_the_thing = true>
struct inner {
    int thing{};
};
template<>
struct inner<false> {};

template <bool include_the_thing>
struct outer {
    inner<include_the_thing> maybe;
};

#include <cassert>

int main() {
    outer<true> has_thing;
    assert(has_thing.maybe.thing == 0);
    outer<false> doesnt_have_thing;
    // assert(doesnt_have_thing.maybe.thing == 0); // compiler error --maybe.thing doesn't exist
}
saxbophone
  • 779
  • 1
  • 6
  • 22
0

Another way to do this would be to have Pool as a base class and the thread safe PoolSafe as a derived class with the necessary specializations. E.g.:

template<typename T>
class Pool
{
protected:
    static constexpr item_type_size_datatype TypeSizeX = sizeof(T) + sizeof(size_t);
public:
    Pool(size_t clusterItemCount) : ClusterItemCount(clusterItemCount),
        ClusterByteSize(clusterItemCount* TypeSizeX)
    {
    }
    virtual ~Pool()
    {
        Clear();
    }
    virtual T* Occupy(bool& outFirstTime)
    {
        //do the occupation
        return result;
    }
};

template<typename T>
class PoolSafe: public Pool<T>
{
private:
    Mutex mutex;
public:
    PoolSafe(size_t clusterItemCount) : Pool<T>(clusterItemCount)
    {
        InitializeCriticalSection(&mutex);
    }
    virtual ~PoolSafe()
    {
        DeleteCriticalSection(&mutex);
    }
    virtual T* Occupy(bool& outFirstTime)
    {
        MutexLock(mutex);
        auto result = Pool<T>::Occupy(outFirstTime);
        MutexUnlock(mutex);
        return result;
    }
};
nielsen
  • 5,641
  • 10
  • 27