4

I would like to alter a classes functions based on the value of a template parameter. Using this post, all of the member functions can be written differently based on the template parameter and that works great. The problem is that example is using the return type of the function so it doesn't work with constructors. Based on this post and this post, I have tried:

// I'm using gcc 4.8.4 so have to define enable_if_t myself although 
// I guess it's not really needed

template< bool B, class T = void >
using enable_if_t = typename std::enable_if<B,T>::type;

template<class T, std::size_t CAPACITY, bool USE_THREADS = true>
class C_FIFO
{
public:
//...other ctor defs

    template<bool trigger = USE_THREADS, enable_if_t<not trigger>* = nullptr >
    C_FIFO(const C_FIFO& that):
        m_buf_capacity(CAPACITY + 1), m_in_ctr(0), m_out_ctr(0), m_wait(true)
    {
        m_buffer_data = that.m_buffer_data;
        m_in_ctr = that.m_in_ctr;
        m_out_ctr = that.m_out_ctr;
        m_wait    = that.m_wait.load();
    }
// more stuff
}

and

template<class T, std::size_t CAPACITY, bool USE_THREADS = true>
class C_FIFO
{
public:
//...other ctor defs

    template<bool trigger = USE_THREADS>
    //enable_if_t<not trigger>
    C_FIFO(const C_FIFO& that, enable_if_t<not trigger, bool> t = false):
        m_buf_capacity(CAPACITY + 1), m_in_ctr(0), m_out_ctr(0), m_wait(true)
    {
        m_buffer_data = that.m_buffer_data;
        m_in_ctr = that.m_in_ctr;
        m_out_ctr = that.m_out_ctr;
        m_wait    = that.m_wait.load();
    }
// other stuff
}

but it both cases the compiler tries to use the default copy constructor which is deleted because the class contains non-copyable types (mutex and condition variable). Both cases seem like they should work, but apparently I'm not seeing something :). Any pointers (no pun intended) would be appreciated.

Community
  • 1
  • 1
schrödinbug
  • 692
  • 9
  • 19
  • 3
    like [this](http://coliru.stacked-crooked.com/a/000be1dbbcb0b18a) ? – Piotr Skotnicki Apr 28 '17 at 20:36
  • I saw that post too--I will try it. I guess since I had seen those other posts and they were closer to what I was already doing (using enable_if), I was hoping to stick with that for consistency. I'm also still trying to wrap my head around the whole SFINAE paradigm, so I was hoping to understand why they didn't work. – schrödinbug Apr 28 '17 at 20:58
  • Also does that method introduce an extra copy operation? – schrödinbug Apr 28 '17 at 20:59
  • No, it does not – Piotr Skotnicki Apr 28 '17 at 21:02
  • It sure doesn't--thank goodness for references. Thanks. The method in that post works. Thanks @PiotrSkotnicki – schrödinbug Apr 29 '17 at 03:40
  • @schrödinbug Using delegating constructors as suggested by @PiotrSkotnicki is definitely the way to go. Throw away the `enable_if` stuff from your constructors. – skypjack Apr 29 '17 at 07:14

2 Answers2

0

Sadly, constructors are less flexible than functions.

using namespace std;
struct S
{
    template<typename T>
    S(typename enable_if<is_same<T, int>::value>::type* = nullptr)
    { cout << "T is int" << endl; }
    template<typename T>
    S(typename enable_if<!is_same<T, int>::value>::type* = nullptr)
    { cout << "T is not int" << endl; }
};

//this works, T is not int
S s;

//these all don't work
auto s = S<int>();
S s<int>();
S s<int>{};

As mentioned here (emphasis added)

Because the explicit template argument list follows the function template name, and because conversion member function templates and constructor member function templates are called without using a function name, there is no way to provide an explicit template argument list for these function templates.

The best you can do is to make a factory function and hope copies are elided, which also mandates a copy/move constructor. Otherwise you can pass in parameters of different types just to "select" the constructor, which is not pretty.

using namespace std;
struct S
{
    template<typename T>
    S(T*, typename enable_if<is_same<T, int>::value>::type* = nullptr);
};

template<typename T>
S make_S(typename enable_if<is_same<T, int>::value>::type* = nullptr);

S s((int*)nullptr);  //god awfully ugly
auto s = make_S<int>();  //hope for elision, must be copyable/moveable
Community
  • 1
  • 1
Passer By
  • 19,325
  • 6
  • 49
  • 96
0

I implemented @PiotrSkotnicki's answer using delegating constructors, but soon ran into a similar issue with the assignment operator (it was actually more problematic because you can't overload it with an extra parameter). With a slight tweak though I was able to solve both issues:

template<class T, std::size_t CAPACITY, bool USE_THREADS = true>
class C_FIFO
{
public:
    C_FIFO(): m_buf_capacity(CAPACITY + 1), m_in_ctr(0), m_out_ctr(0),
        m_wait(true) { }

    // copy constructor needs to be fancy because the mutex is not copyable or
    // movable.  Uses private helper functions to pick single or
    // multi-threaded version

    C_FIFO(const C_FIFO &that):m_buf_capacity(CAPACITY + 1)
    {
        copy_fifo(that, std::integral_constant<bool, USE_THREADS>{});
    }

    C_FIFO& operator=(const C_FIFO& that)
    {
        return copy_fifo(that, std::integral_constant<bool, USE_THREADS>{});
    }
// other public methods here

private:
    C_FIFO& copy_fifo(const C_FIFO& that, std::true_type)
    {
        std::unique_lock<std::mutex> this_lock(this->m_lock, std::defer_lock);
        std::unique_lock<std::mutex> that_lock(that.m_lock, std::defer_lock);
        std::lock(this_lock, that_lock);

        m_buffer_data = that.m_buffer_data;
        m_in_ctr = that.m_in_ctr;
        m_out_ctr = that.m_out_ctr;
        m_wait    = that.m_wait.load();
        return *this;
    }


    C_FIFO& copy_fifo(const C_FIFO& that, std::false_type)
    {
        m_buffer_data = that.m_buffer_data;
        m_in_ctr = that.m_in_ctr;
        m_out_ctr = that.m_out_ctr;
        m_wait    = that.m_wait.load();
        return *this;
    }

};
schrödinbug
  • 692
  • 9
  • 19