По избор публикувайте методи, базирани на различни параметри на шаблона

Предговор

Представете си, че имам шаблон: template<class... Opts> class rqueue, който може да има различни функции, избрани чрез тагове (структури със специални опции), предадени към списъка с параметри, напр.

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

Първата версия (trace) е опашка от записи, която да се използва за записване на записите за проследяване (opt::fixed_size ограничава размера си до 4096B). Втората версия (info) ще бъде запълнена от първата (от някаква нишка, която ще пренапише записите с преобразуване в плоско представяне), но важното е, че opt::virtual_offset<>, което добавя тези методи:

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

и различни други характеристики (offset_to_iterator()), базирани на това виртуално отместване, което винаги нараства (с всеки запис), което симулира виртуална памет с размер напр. 4 GB (когато unsigned се използва като отместване, може да бъде дори по-голямо с size_t или unisgned long long), където действителният буфер (с размер напр. 4096B) създава прозорец вътре в тази виртуална памет.

Връзка към друг мой свързан въпрос - помощник за пакет опции който е специално проектиран за този шаблон.

(Имайте предвид, че вероятно има много други функции, които могат да се комбинират независимо, напр. opt::rqueue_listener, които могат да се използват за докладване на различни събития).

Проблемът

Успях да създам този шаблон с всички възможни функции, където някои методи са фиктивни, когато функцията не е избрана (напр. start() връща нула и stop() е същото като size() в този случай), но аз бих искал да скрия методите по някакъв начин, ако функцията не е избрана. Някаква идея?

(Друг пример би бил фиктивно set_listener(void*), ако opt::rqueue_listener не е включено - опцията може да се комбинира с всяка друга опция.)

РЕДАКТИРАНЕ: Представете си напр. using off_t = conditional_t<something,the_type,void> и private: off_t start_(). Това, което искам е да:

  1. Накарайте public: off_t start() да се обади на това start_(), ако off_t не е void
  2. Няма метод start(), ако off_t е невалиден (или е изпълнено някакво условие). Като алтернатива някои static_assert, ако се опитам да го извикам.

Моите опити

Мислех да слея класа с разширители, които ще публикуват функциите, като се прехвърлят (*this) към реалния клас (rqueue<...>&) и пренасочват извикванията там (към частни методи, където разширителят става приятел). Създадох друг template<class... Bases> class merge помощник, който може да наследи всеки избран клас, като същевременно игнорира всички void предадени. Получи се, но решението е доста кофти, не ми харесва.

Друго възможно решение, което ми мина през ума, беше да създам някаква основна реализация (като различен шаблон, вероятно скрит в някои namespace detail) и да използвам верига от специализации на шаблони, които ще публикуват методите въз основа на опциите. Проблемът е, че броят на комбинациите нараства бързо и може да има друг проблем с friend-ing на класа, за да има достъп до частни методи на записа (първият параметър, предаден на шаблона).

Моите опити за SFINAE и static_assert често завършваха с грешки на компилатора, оплаквайки се, че специализацията на метода не е разрешена в шаблони (или частични специализации) или static_assert се задейства, когато не трябва. Очаквам да има някакво хубаво решение. Очакваме с нетърпение да го видим :)


person firda    schedule 13.10.2014    source източник
comment
винаги ли са opt::features шаблони с един тип параметър на шаблон?   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Всяка функция е структура (евентуално шаблон с други параметри, но предавана като клас, а не като шаблон). Не мисля, че това е толкова важно. Вижте свързания въпрос за повече подробности.   -  person firda    schedule 13.10.2014
comment
напр. fill_empty приема два параметъра - тип и стойност: template <class T, T V> struct fill_empty: tag::fill_empty { typedef T type; static constexpr T value = V; };   -  person firda    schedule 13.10.2014
comment
добре, може би не разбирам какво целиш, накратко: винаги прилагаш start() членска функция, но искаш тя да извиква virtual_offset<>::start(), когато класът ти наследява от virtual_offset<>, в противен случай да бъде недостъпна?   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Имам предвид не наследяване, а съществува в пакета с параметри на шаблон   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Не искам моят шаблон изобщо да има public: off_t start(), ако opt::virtual_offset<...> не е включен (off_t може да получи всичко в този случай, моят opt::bind ще ви помогне да го дефинирате void, ако имате нужда).   -  person firda    schedule 13.10.2014
comment
могат ли тези членски функции да бъдат шаблонирани или планирате да ги направите виртуални в бъдеще или нещо подобно?   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Вижте редакцията за по-добро обяснение. Не искам никакви virtual, това е template проектирано да бъде много light-weight, ако не е включена специална функция (напр. rqueue<trace_record> ще бъде основният, без допълнителни данни и методи).   -  person firda    schedule 13.10.2014
comment
Така че, да, те могат да бъдат шаблонирани, ако това ще помогне за решаването му (чрез предоставяне на някои шаблонни аргументи по подразбиране със SFINAE).   -  person firda    schedule 13.10.2014
comment
защо static_assert(!std::is_same<off_t, void>::value, "!"); в публичната start() членска функция не е опция?   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Мисля, че имах проблем с това, когато се използваше в основната версия - компилаторът задейства assert, без да е необходимо да извиква метода. пак ще проверя.   -  person firda    schedule 13.10.2014
comment
Хм, изглежда работи. Не знам какво правех грешно последния път, когато опитах нещо подобно. Вероятно ще го приема, ако го публикувате като отговор. Сега трябва да му отделя малко време.   -  person firda    schedule 13.10.2014
comment
нека изчакаме, може би някой има по-добра идея. И аз съм любопитен   -  person Piotr Skotnicki    schedule 13.10.2014
comment
Е, липсата на метода изобщо (или някой SFINAE да го крие) би било много хубаво, но мисля, че мога да продължа с това static_assert. Трябва да съм работил твърде много последния път, за да пропусна това просто решение :D   -  person firda    schedule 13.10.2014
comment
Този въпрос изглежда има подобни отговори, въпреки че заглавието е напълно различно.   -  person firda    schedule 13.10.2014


Отговори (2)


Опция 1

Използвайте static_assert и факта, че членските функции на шаблона се създават само при поискване, когато се изисква от контекста:

#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;
    }
};

Въпреки това, достъпът до start(), когато condition е невярно, ще задейства грешка при статично твърдение:

error: static assertion failed: !

ДЕМО 1

Вариант 2

Използвайте някои грозни SFINAE, като направите членската функция и шаблон:

#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;
    }
};

Това ще задейства грешка:

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

ДЕМО 2

Вариант 3

Използвайте някакъв помощен базов клас с CRTP, който прилага метода условно:

#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;
    }
};

Съобщението за грешка на condition = false; е съвсем ясно:

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

ДЕМО 3

person Piotr Skotnicki    schedule 13.10.2014
comment
Третата версия по някакъв начин е подобна на моето работещо решение с тези разширители, но изглежда по-добре (чрез предаване на условието вместо моя merge<conditional_t<condition,rqueue<Opts...>,void>>) и отговаря точно на изискванията (като няма методите, ако не са необходими). Благодаря много. - person firda; 13.10.2014

Следният код е това, което опитах след подсказката от Piotr S.:
(представете си using namespace std в тази заглавка, въпреки че е малко по-различно.)

#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
}

Имах някои проблеми с подобен static_assert в моя реален код, но не мога да си спомня защо компилаторът го е задействал в основната версия... вероятно моя грешка, като го извиках там, където не трябва да бъде. Мисля, че наличието на едно start_() за вътрешна употреба (за да го фалшифицирате, ако не се използва) и наличието на едно public: start() с assert (и като се уверите, че не го извиквате в рамките на шаблона) решава проблема. Ще изчакам известно време (и с радост ще приема различен отговор, ако го има).

person firda    schedule 13.10.2014