C ++, я могу включить несколько специализаций класса черт, используя enable_if вместо copy-paste? - PullRequest
0 голосов
/ 08 мая 2018

Итак, у меня есть класс base, не содержащий шаблонов, который содержит настройки по умолчанию для его методов.Затем я пытаюсь использовать наследование с шаблонным классом.Вот фрагмент примера кода для иллюстрации.

// enums for as a template selector
enum class version
{
    ver1,
    ver2,
    ver3
};

// Base class with fabricated methods
struct base
{
    virtual void propertyOne()
    {
        // some default action
    }

    virtual void propertyTwo()
    {
        // some default action
    }
};  



// derived class
template <version V>
struct derived : public base
{
    virtual void propertyOne()
    {
        helper< One, V >();
    }

    virtual void propertyTwo()
    {
        helper< Two, V >();
    }
}

Я использую вспомогательную функцию для выполнения «универсального» алгоритма для различных «полей», которые используются в признаках класса.

Например: поле является чем-то похожим на это

struct field
{
    int thingone;

    constexpr field(int i):thingone(i){}
};

В c ++ 11, чтобы дать внешним полям экземпляры полей, я обернул их как статические члены другой структуры (c ++ 14 ослабляет эти правила,Ну что ж).Вся причина, по которой я это делаю, заключается в том, что мне нужны значения константных выражений (например, переменная-член thingone необходима в качестве параметров шаблона другого метода, который требует, чтобы она была константным выражением).

struct fields
{
    static constexpr field One{1};
    static constexpr field Two{2};
};

// defining trait class from structure above
template< const field& T, revision R >
class fieldTraits;

// sample fieldTrait definitions for illustrative purposes

template< >
class fieldTraits< fields::One, revision::ver3>
{
    public:
        // Let's say I have common field names
        // with different constants that I want to plug
        // into the "helper" algorithm
        static constexpr size_t field_val = 1; 
};

template< >
class fieldTraits< fields::Two, revision::ver1>
{
    public:
        // Let's say I have common field names
        // with different constants that I want to plug
        // into the "helper" algorithm
        static constexpr size_t field_val = 1; 
};  

// Main guts of the class methods above
template< const field& F, revision R, typename TT = traitClass<F,R> >
void helper()
{
    // Let's pretend I'm doing something useful with that data
    std::cout << F.thingone << std::endl;
    std::cout << TT::field_val << std::endl;
}

Проблема, с которой я столкнулся, заключается в попытке создать экземпляр, например,

derived<revision::rev1> l_derived;

Поскольку я определил класс черт только для ver3, я не могу создать экземпляр класса без определения класса черт для ver1 и ver2 явно.Но если классы признаков одинаковы, скажем, для ver1 - ver 3, есть ли какое-либо условие enable_if, которое я должен был сделать этим классом шаблона действительным для всех revs <= ver3?

Я не смог найти ничего в заголовке traits_type, который в основном предлагает проверки типа "время компиляции", такие как std::is_same и т. Д.

Я знаю, один вариант - скопировать и вставить чертуклассы для ver1 - ver3, но это казалось излишним, поскольку я хотел избежать копирования повторяющегося кода.

Другой вариант - создать разные классы для каждой ревизии и использовать динамический полиморфизм, где я могу определитькласс для каждой ревизии.И тогда мне нужно только включить изменения ревизий черт, где они необходимы.Например,

class derived_ver1 : public base
{
    virtual void propertyOne()
    {
        helper< fields::One, revision::ver1 >();
    }

    virtual void propertyTwo()
    {
        helper< fields::Two, revision::ver1 >();
    }
};

class derived_ver2 : public derived:ver1
{
    virtual void propertyTwo()
    {
        helper< fields::Two, revision::ver2 >();
    }
};

class derived_ver3 : public derived:ver2
{
    virtual void propertyTwo()
    {
        helper< Two, revision::ver3 >();
    }
};

В этом примере propertyOne() может повторно использовать класс признаков из ревизии 1 для ревизии 2 и 3, поскольку он не изменился после предыдущей ревизии 1 (и позволяет избежать копирования и вставки черт).

Есть ли лучший дизайн, к которому я мог бы подойти?

В итоге: есть ли способ использовать мое исходное наследование шаблонов и использовать некоторую особенность шаблона (например, std::enable_if), чтобы повторно использовать класс признаков для неопределенных ревизий.Вместо того, чтобы явно определять черту для каждой ревизии (которая приводит к копированию-вставке).

Или второй подход, использующий динамический полиморфизм, является лучшим способом сделать это (который я читал, увеличил стоимость поиска в vtable, чтоприходит с этим)?

1 Ответ

0 голосов
/ 08 мая 2018

Мне очень трудно понять, чего ты хочешь; пожалуйста, уточните свой вопрос, чтобы проверить детали (version и revision совпадают? fieldTraits и traitsClass совпадают?).

В любом случае, если я правильно понимаю, вы хотите определить специализацию для

template <const field& T, revision R>
class fieldTraits;

когда R равно val1 или val2 или val3 и, возможно, других специализаций для отдельных следующих значений.

Предположим, у вас есть четыре ревизии

enum class revision { ver1, ver2, ver3, ver4 };

Вы можете объявить fieldTraits (как struct, чтобы сделать его короче), добавив bool параметр шаблона по умолчанию, который говорит, что R <= revision::ver3

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

Теперь вы можете разработать специализацию для ver1, ver2 и ver3

template <field const & T, revision R>
struct fieldTraits<T, R, true>
 { static constexpr size_t field_val = 1; };

и специализация для ver4

template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };

Ниже приведен упрощенный, но полный пример

#include <iostream>

enum class revision { ver1, ver2, ver3, ver4 };

struct field
 {
   int thingone;

   constexpr field (int i) : thingone(i)
    { }
 };

template <field const & T, revision R, bool = (R <= revision::ver3)>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, true>
 { static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };

static constexpr field f{1};

int main ()
 {    
   std::cout << "ver1: " << fieldTraits<f, revision::ver1>::field_val
      << std::endl;
   std::cout << "ver2: " << fieldTraits<f, revision::ver2>::field_val
      << std::endl;
   std::cout << "ver3: " << fieldTraits<f, revision::ver3>::field_val
      << std::endl;
   std::cout << "ver4: " << fieldTraits<f, revision::ver4>::field_val
      << std::endl;
 }

этот отпечаток

ver1: 1
ver2: 1
ver3: 1
ver4: 2

- РЕДАКТИРОВАТЬ -

После комментария ОП я предлагаю немного другое решение

template <field const & T, revision R, typename = std::true_type>
struct fieldTraits;

// version ver1, ver2, ver3 cases
template <field const & T, revision R>
struct fieldTraits<T, R, std::integral_constant<bool, (R <= revision::ver3)>>
 { static constexpr size_t field_val = 1; };

// version ver4 case
template <field const & T>
struct fieldTraits<T, revision::ver4>
 { static constexpr size_t field_val = 2; };
...