Есть ли в C ++ шаблон проектирования, который облегчает запросы данных разных типов с единым интерфейсом функции? - PullRequest
0 голосов
/ 08 января 2019

Я хочу предоставить одно объявление / определение функции, которое возвращает правильный тип данных на основе входного аргумента. Это похоже на то, для чего предназначен шаблон функции, но более конкретно, я бы хотел, чтобы интерфейс функции выглядел следующим образом:

template<class InT>
RetT getData(InT*);

где,

  • Требование 1: RetT зависит от типа ввода InT и не обязательно равно InT .
  • Требование 2: Кроме того, я хочу применить общий интерфейс для всех действующих типов InT , чтобы определить, что такое RetT . Другими словами, надеюсь, InT должен быть базовым классом.

Немного о фоновом приложении. Допустим, у меня есть система обработки текста, для которой я могу указать различные конфигурации. Некоторые конфигурации могут быть флагами (т. Е. Логическими значениями), например executeCompact , addSpacing и т. Д. Некоторые конфигурации могут быть токенами (т. Е. Строковыми литералами), например prefixToPath , surfixToLastName и т. Д. Некоторые конфигурации могут иметь пользовательские типы данных, например textColorMap , fontFamily и т. Д.

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

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

Ниже приведен пример кода. Это удовлетворяет обоим требованиям, однако мне все еще интересно, есть ли способ избежать шаблона класса для Requirement2.

#include <iostream>
using namespace std;

// In a *.hpp file in component A
template<class T>
auto getData(T* spec) {
    return spec->data();
}

template<class DataT>
class Configuration {
    public:
    virtual DataT data() = 0; 
};

// In some other components source files
class PerformCompact : Configuration<bool> {
    private:
    bool _data;
    public:
    PerformCompact(bool d):_data(d){}
    bool data() override {return _data;}

};

class PrefixToPath : Configuration<string>{
    private:
    string _data;
    public:
    PrefixToPath(string d):_data(d){}
    string data() override {return _data;}
};

// In one application source file
int main()
{
   PerformCompact performCompact(true);
   PrefixToPath prefixToPath("Some string");
   auto pd = getData(&performCompact);
   auto pstr = getData(&prefixToPath);
   cout << pd << endl;
   cout << pstr << endl;

   return 0;
}

Ответы [ 2 ]

0 голосов
/ 08 января 2019

Можно использовать SFINAE вместе с новыми метафункциями из <type_traits> для проверки условий типов в обратной строке функции. Если условия не выполняются для некоторых шаблонов функций и не выполняются для других, SFINAE следит за тем, чтобы компилятор шел дальше, и выбирал наилучшего кандидата для шаблона функции.

Это работает, конечно, если условия ваших типов можно проверить во время компиляции. В этом небольшом примере проверяется, могут ли два типа присваиваться друг другу, а затем включается тип возврата. Если типы нельзя назначить (комбинации C с (A, B)), компиляция завершится неудачно:

#include <type_traits>
#include <cassert>

class A; 
class B; 
class C; 

class A 
{
    public: 
        void operator=(B const& b) {}; 
};  

class B 
{
    public: 
        void operator=(A const& a) {}; 
}; 

class C {}; 

template<class RetT, class InT>
std::enable_if_t
<
    std::is_assignable<InT,RetT>::value, 
    RetT
>
getData(InT*)
{
    RetT result; 

    return result;
};

using namespace std; 

int main()
{
    A* Aptr = new A;
    B* Bptr = new B;
    C* Cptr = new C;

    getData<A>(Bptr); 
    getData<B>(Aptr); 


    // Assignments not there, so the compilation fails. 
    getData<B>(Cptr); 
    getData<C>(Bptr); 
    getData<A>(Cptr); 
    getData<C>(Aptr); 

    delete Aptr; 
    Aptr = nullptr;
    delete Bptr; 
    Bptr = nullptr; 
    delete Cptr; 
    Cptr = nullptr;

    return 0;
};

Вы также можете использовать if constexpr в теле функции, если вы можете использовать C ++ 17:

template<class RetT, class InT>
RetT
getData(InT*)
{
    if constexpr (std::is_assignable<InT,RetT>::value)
    {
        RetT result; 

        return result;
    }
    else assert(false && "RetT and IntT not assignable.");
};

if constexpr оценивается во время компиляции и если true

   RetT result; 

   return result;

компилируется, в противном случае оператор assert компилируется. Если вы попытаетесь использовать функцию с комбинациями C, (A, B), вы получите ошибку времени выполнения:

 RetT getData(InT*) [with RetT = B; InT = C]: Assertion `false && "RetT and IntT not assignable."' failed.

Если вы хотите, чтобы подтверждение активировалось во время компиляции, вы можете использовать static_assert.

0 голосов
/ 08 января 2019

Вы можете определить общее требование для InT, чтобы явно определить тип возвращаемого значения, например:

class PerformCompact : Configuration<bool> {
    typedef bool DataReturnType;
    ...
}

и затем вы можете определить свою функцию как:

template<class T>
typename T::DataReturnType getData(T* spec) {
    return spec->data();
}

Если ваш тип возвращаемого значения всегда аргумент шаблона Configuration, тогда это немного облегчает задачу. Вместо того, чтобы явно определять его в каждом производном типе, вы можете просто поместить его в базовый класс:

template<class DataT>
class Configuration {
    public:
    virtual DataT data() = 0; 
    typedef DataT DataReturnType;
};

Если вы хотите избежать явного определения типа, в C ++ 17 вы можете оставить его компилятору для его самостоятельного вывода, используя ключевое слово auto:

template<class T>
auto getData(T* spec) {
    return spec->data();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...