Опционально опубликуйте методы, основанные на вариативных параметрах шаблона.

Предисловие

Представьте, что у меня есть шаблон: 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 ограничивает ее размер до 4096 байт). Вторая версия (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 ГБ (когда в качестве смещения используется unsigned, оно может быть еще больше при size_t или unisgned long long), где фактический буфер (размером, например, 4096 Б) создает окно внутри этой виртуальной памяти.

Ссылка на мой другой связанный вопрос - помощник пакета параметров который был специально разработан для этого шаблона.

(Обратите внимание, что существует множество других функций, которые можно комбинировать независимо друг от друга, например opt::rqueue_listener, которые можно использовать для сообщения о различных событиях).

Проблема

Мне удалось создать этот шаблон со всеми возможными функциями, где некоторые методы являются фиктивными, когда функция не выбрана (например, start() возвращает ноль, а stop() в этом случае совпадает с size()), но я хотел бы как-то скрыть методы, если функция не выбрана. Есть идеи?

(Еще одним примером может быть фиктивный set_listener(void*), если opt::rqueue_listener не было включено — этот параметр можно комбинировать с любым другим параметром.)

EDIT: Представьте себе, например. 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-ингом класса для доступа к приватным методам записи (первый параметр передается в шаблон).

Мои попытки 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
Я думаю, что у меня была проблема с этим при использовании в базовой версии - компилятор запускал утверждение без необходимости вызывать метод. Я проверю снова.   -  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 имеет значение false, вызовет статическую ошибку утверждения:

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
Третья версия чем-то похожа на мое рабочее решение с этими extenders, но выглядит лучше (за счет передачи условия вместо моего merge<conditional_t<condition,rqueue<Opts...>,void>>) и точно соответствует требованиям (за счет отсутствия методов, если они не нужны). Большое спасибо. - person firda; 13.10.2014

Следующий код — это то, что я попробовал после подсказки Петра С.:
(представьте себе 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() с утверждением (и убедиться, что он не вызывается в шаблоне) решает проблему. Я подожду некоторое время (и с радостью приму другой ответ, если он будет).

person firda    schedule 13.10.2014