0

Preface

Imagine I have a template: template<class... Opts> class rqueue, which can have various features selected by tags (special options structures) passed to the parameter list, e.g.

rqueue<trace_record, opt::fixed_size<>> trace;
rqueue<trace_record::flat, opt::fixed_size<>, opt::virtual_offset<>> info;

The first version (trace) is a record queue to be used for writing the trace records (the opt::fixed_size limits its size to 4096B). The second version (info) will get filled from the first one (by some thread, that will rewrite the records with conversion to flat representation), but the important thing is that opt::virtual_offset<> which adds these methods:

off_t start(); // virtual offset of oldest record
off_t stop();  // virtual offset of future record (when next gets written)

and various other features (offset_to_iterator()) based on this virtual offset that is always growing (with each record), which simulates virtual memory of size e.g. 4 GB (when unsigned used as the offset, it could be even larger with size_t or unisgned long long), where the actual buffer (of size e.g. 4096B) creates a window inside that virtual memory.

Link to my other related question - option pack helper which was specifically designed for this template.

(Note that thare are possibly many other features that can be independently combined, e.g. opt::rqueue_listener that can be used to report various events).

The Problem

I have managed to create that template with all the possible features, where some methods are dummy when the feature was not selected (e.g. start() is returning zero and stop() is same as size() in that case), but I would like to hide the methods somehow, if the feature was not selected. Any idea?

(Another example would be dummy set_listener(void*) if the opt::rqueue_listener was not included - the option can be combined with any other option.)

EDIT: Imagine e.g. using off_t = conditional_t<something,the_type,void> and private: off_t start_(). What I want is to:

  1. Have public: off_t start() calling that start_() if off_t is not void
  2. Have no method start() if off_t is void (or some condition met). Alternatively some static_assert if I try to call it.

My Attempts

I was thinking about merging the class with extenders that would publish the functions by casting itself (*this) to the real class (rqueue<...>&) and redirecting the calls there (to private methods, where the extender is made a friend). I have created another template<class... Bases> class merge helper that could inherit any class selected while ignoring any void passed. It worked, but the solution is quite ugly, I don't like it.

Another possible solution that crossed my mind was to create some basic implementation (as a different template, possibly hidden in some namespace detail) and use a chain of template specializations that would publish the methods based on the options. Problem is that the number of combinations is growing rapidly and there could be another problem with friend-ing the class to have access to private methods of the record (first parameter passed to the template).

My SFINAE and static_assert attempts often ended with compiler errors, complaining that method specialization is not allowed in templates (or partial specializations) or the static_assert got fired when it should not. I expect there is some nice solution. Looking forward to see it :)

Community
  • 1
  • 1
firda
  • 3,268
  • 17
  • 30
  • are opt::features always templates with one template type-parameter? – Piotr Skotnicki Oct 13 '14 at 09:41
  • Each feature is a structure (possibly template with other parameters, but passed as a class, not as a template). I don't think that is that important. See the linked question for more details. – firda Oct 13 '14 at 09:43
  • E.g. `fill_empty` takes two parameters - type and value: `template struct fill_empty: tag::fill_empty { typedef T type; static constexpr T value = V; };` – firda Oct 13 '14 at 09:47
  • ok, maybe I don't understand what you aim to, in short: you always implement `start()` member function, but you want it to call `virtual_offset<>::start()` when your class inherits from `virtual_offset<>`, otherwise to be inaccessible? – Piotr Skotnicki Oct 13 '14 at 09:48
  • I mean not *inherit* but *exists within template parameter pack* – Piotr Skotnicki Oct 13 '14 at 09:50
  • I don't want my template to have `public: off_t start()` at all if `opt::virtual_offset<...>` was not included (`off_t` can get anything in that case, my `opt::bind` will help to define it `void` if you need). – firda Oct 13 '14 at 09:51
  • can those member functions be templated, or you are planning to make them virtual in future or smth ? – Piotr Skotnicki Oct 13 '14 at 09:55
  • See the edit for better explanation. I don't want any `virtual`, this is `template` designed to be very `light-weight` if no special feature included (e.g. `rqueue` will be the basic, without additional data and methods). – firda Oct 13 '14 at 09:58
  • So, yes, they can be templated if it will help solving it (by providing some default template args with SFINAE). – firda Oct 13 '14 at 09:59
  • 1
    why `static_assert(!std::is_same::value, "!");` in the public `start()` member function is not an option? – Piotr Skotnicki Oct 13 '14 at 10:05
  • I think I had a problem with this, when used on basic version - the compiler triggered the assert without the need to call the method. I will check again. – firda Oct 13 '14 at 10:06
  • Hmm, seems to work. I don't know what I was doing wrong the last time I did try something like that. I will probably accept it if you post it as an answer. Have to give it some time now. – firda Oct 13 '14 at 10:13
  • let's wait, maybe someone has better idea. I'm curious too – Piotr Skotnicki Oct 13 '14 at 10:14
  • Well, not having the method there at all (or some SFINAE hiding it) would be very nice, but I think I can go with this `static_assert`. I must have been working too much the last time to miss that simple solution :D – firda Oct 13 '14 at 10:16
  • [This question](http://stackoverflow.com/questions/25486033/c-class-template-specialization-without-having-to-reimplement-everything/25486107#25486107) seems to have similar answers althought the title is completely different. – firda Oct 13 '14 at 14:25

2 Answers2

2

The following code is what I did try after the hint from Piotr S.:
(imagine using namespace std inside that header, although it is a bit different.)

#include "basics.hpp"
using namespace firda;

template<class Offset = void> struct helper {
    Offset start_() const { return 0; }
};
template<> struct helper<void> {
    void start_() const {}
};

template<class Offset = void> class rqueue
  : private helper<Offset> {
public:
    Offset start() const {
        static_assert(!is_same<Offset,void>::value, "!!");
        return this->helper<Offset>::start_();
    }
};

int main() {
    rqueue<> one;
    rqueue<uint> two;
    cout << two.start();
//  one.start(); -- assert triggered
}

I had some problems with similar static_assert in my real code, but cannot remember why the compiler triggered it in basic version.... probably my mistake by calling it where it should not be. I think that having one start_() for internal usage (to fake it if not used) and having one public: start() with the assert (and making sure not to call it withitn the template) solves the problem. I will wait a while (and gladly accept different answer if it will be there).

firda
  • 3,268
  • 17
  • 30
1

Option 1

Make use of static_assert and the fact that template's member functions are instantiated only on demand when required by the context:

#include <iostream>
#include <type_traits>

constexpr bool condition = true;

template <class... Opts>
class rqueue
{
    using off_t = std::conditional_t<condition, int, void>;

public:
    off_t start()
    {
        static_assert(!std::is_same<off_t, void>::value, "!");
        return start_();
    }

private:
    off_t start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

That said, access to start() when condition is false will trigger static assertion error:

error: static assertion failed: !

DEMO 1

Option 2

Use some ugly SFINAE, by making the member function a template as well:

#include <iostream>
#include <type_traits>

constexpr bool condition = true;

template <class... Opts>
class rqueue
{
    using off_t = std::conditional_t<condition, int, void>;

public:
    template <typename T = void>
    std::enable_if_t<!std::is_same<off_t, void>::value, off_t> start()
    {
        return start_();
    }

private:
    off_t start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

This will trigger error:

error: no type named 'type' in 'struct std::enable_if<false, void>'

DEMO 2

Option 3

Use some helper base class with CRTP, that implements the method conditionally:

#include <iostream>
#include <type_traits>

constexpr bool condition = true;

template <bool condition, typename CRTP>
struct start_base {};

template <typename CRTP>
struct start_base<true, CRTP>
{    
    int start()
    {
        return static_cast<CRTP*>(this)->start_();
    }
};

template <class... Opts>
class rqueue : public start_base<condition, rqueue<Opts...>>
{
    friend class start_base<condition, rqueue<Opts...>>;

private:
    int start_()
    {
        std::cout << "start_()" << std::endl;
        return 1;
    }
};

The error message on condition = false; is quite clear:

error: 'class rqueue<>' has no member named 'start'

DEMO 3

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • The third version is somehow similar to my working solution with those *extenders* but looks nicer (by passing the condition instead of my `merge,void>>`) and meets the requirements exactly (by not having the methods if not needed). Thanks a lot. – firda Oct 13 '14 at 11:05