c ++ 11 — упрощение дизайна выходного класса C ++ на основе CRTP


Исходная информация

Я работаю над шаблоном проектирования, похожим на поток данных. Представленные ниже классы предназначены для представления механизма распределения выходных данных. level1 базовый класс CRTP getOutput<N> в level1 это функция, которую можно использовать для получения выходных данных из экземпляра производного класса. В зависимости от параметра шаблона N он вызывает один из определенных пользователем методов getOutputImpl, Эти методы предназначены для предоставления в производном классе (в стиле CRTP). Каждый из методов getOutputImpl определяет порт вывода, связанный с определенным пользователем производным классом. Тип ввода метода getOutputImpl определяется дизайном. Типы вывода методов getOutputImpl может изменяться. Однако по типу выходной тип должен иметь структуру std::unique_ptr<TOutputType>где TOutputType может быть любого класса. Более подробную информацию можно найти здесь: предыдущий вопрос.


Вопрос

Чтобы разрешить автоматическое распознавание количества определенных пользователем портов (то есть методов getOutputImpl) метод getOutputPortsNumber(void) предоставляется в базе level1 учебный класс. Этот метод основан на идее, что возвращаемый тип всех пользовательских методов getOutputImpl является std::unique_ptr<TOutputType>, Таким образом, можно определить дополнительный getOutputImpl метод в базовом классе, который не имеет этого возвращаемого типа (например, он имеет void тип возврата: void getOutputImpl(...)).

Описанная выше методология работает, если void getOutputImpl(...) определяется в определяемом пользователем производном классе (DataflowOutputClass в этом примере) вместе с другими определенными пользователем std::unique_ptr<TOutputType> getOutputImpl(...) методы. Тем не менее, когда дополнительный void getOutputImpl(...) метод перенесен на базу level1 класс, я получаю ошибку компиляции: no matching function for call to 'DataflowOutputClass<int>::getOutputImpl(PortIdxType<2ul>, const PolyIndex&) const,


Код

typedef size_t Index;
typedef unsigned long Natural;
typedef std::vector<Index> PolyIndex;
typedef const PolyIndex& crPolyIndex;
template<Index N> struct PortIdxType{};

template<typename TLeafType>
class level1
{

public:

TLeafType* asLeaf(void)
{return static_cast<TLeafType*>(this);}

TLeafType const* asLeaf(void) const
{return static_cast<TLeafType const*>(this);}

template <Index N>
auto getOutput(crPolyIndex c_Idx) const
{return asLeaf() -> getOutputImpl(PortIdxType<N>{}, c_Idx);}

static constexpr Natural getOutputPortsNumber(void)
{return getOutputPortsNumberImpl<0>();}

template<Index N>
static constexpr std::enable_if_t<
std::is_void<
decltype(
std::declval<TLeafType*>() ->
getOutput<N>(PolyIndex({}))
)
>::value,
Index
> getOutputPortsNumberImpl(void)
{return N;}

template<Index N>
static constexpr std::enable_if_t<
!std::is_void<
decltype(
std::declval<TLeafType*>() ->
getOutput<N>(PolyIndex({}))
)
>::value,
Index
> getOutputPortsNumberImpl(void)
{return getOutputPortsNumberImpl<N + 1>();}

template<Index N>
void getOutputImpl(
PortIdxType<N>, crPolyIndex c_Idx
) const
{throw std::runtime_error("Wrong template argument.");}};

template<typename T>
class DataflowOutputClass:
public level1<DataflowOutputClass<T>>
{
public:

// if void getOutputImpl(...) const is moved here from level1,
// then the code compiles and works correctly.

//overload for when N = 0
std::unique_ptr<double> getOutputImpl(
PortIdxType<0>, crPolyIndex c_Idx
) const
{
std::unique_ptr<double> mydouble(new double(10));
return mydouble;
}

//overload for when N = 1
std::unique_ptr<int> getOutputImpl(
PortIdxType<1>, crPolyIndex c_Idx
) const
{
std::unique_ptr<int> myint(new int(3));
return myint;
}

};int main()
{
DataflowOutputClass<int> a;
std::cout << a.getOutputPortsNumber() << std::endl;
}

1

Решение

В исходном коде я определил три проблемы:

  1. std::declval<TLeafType*>() -> getOutput пытается найти имя в неполном классе.

  2. std::declval<TLeafType*>() -> getOutput<N> не называет шаблон функции getOutput,

  3. getOutputImpl Объявления в производном классе скрывают все функции-члены с тем же именем базового класса.


Выражение std::declval<TLeafType*>() -> getOutput используется в типе возврата DataflowOutputClass::getOutputPortsNumberImpl,

Создание шаблона класса приводит к созданию объявлений всех функций-членов. Когда вы получаете с CRTP через level1<DataflowOutputClass<T>> в DataflowOutputClass класс, компилятор должен создать экземпляр level1<..> перед созданием производного класса. Следовательно, во время создания level1<DataflowOutputClass<T>>, DataflowOutputClass<T> класс все еще не завершен.

Обходной путь должен отложить определение типа возврата DataflowOutputClass::getOutputPortsNumberImpl сделав его зависимым от параметра шаблона шаблона функции:

template<Index N, typename T = TLeafType>
static constexpr std::enable_if_t<
std::is_void<
decltype(
std::declval<T*>() ->
getOutput<N>(PolyIndex({}))
)
>::value,
Index
> getOutputPortsNumberImpl(void)
{return N;}

Теперь тип возвращаемого значения зависит от параметра-шаблона. шаблона функции. Этот тип возврата может быть разрешен только тогда, когда функция создается экземпляр. Функция создается неявно через использование getOutputPortsNumber в mainгде производный класс уже завершен.

Обратите внимание, что нет необходимости искать имя getOutput в области производного класса, вы также можете по умолчанию T = level1, Мы бы не искали имя в производном классе, если бы использовали:

template<Index N, typename T = TLeafType>
static constexpr std::enable_if_t<
std::is_void<
decltype(
getOutput<N>(PolyIndex({}))
)
>::value,
Index
> getOutputPortsNumberImpl(void)
{return N;}

Однако, чтобы определить тип возвращаемого значения этого getOutputPortsNumberImpl, создание определения getOutput необходимо, потому что getOutput использует вычет типа возврата. Его определение будет страдать от проблемы, аналогичной исходному коду: он пытается найти имя в неполном виде.

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


В # 1 мы уже заменили это на std::declval<T*>() -> getOutput<N>(PolyIndex({})), но вопрос тот же. Рассматривать:

bool operator> (bool, PolyIndex);

// class template level1

struct derived
{
int getOutput;
};

С этой настройкой, выражение как declval<T*>() -> getOutput<N>(PolyIndex({})) может быть проанализирован как:

(
(declval<T*>()->getOutput)  <  N
)
>
(
PolyIndex({})
)

То есть, (x < N) > PolyIndex{},

Чтобы позволить компилятору понять, что getOutput шаблон, используйте template ключевое слово:

std::declval<T*>() -> template getOutput<N>(PolyIndex{})

(Дополнительный () инициализировать PolyIndex ненужны.)


В общем, любой член в производном классе скрывает членов с одинаковыми именами базовых классов. Чтобы перегрузить функцию-член в производном классе с помощью функций-членов в базовом классе, вы можете использовать используя декларирование «внедрить» имя базового члена в производный класс:

template<typename T>
class DataflowOutputClass:
public level1<DataflowOutputClass<T>>
{
public:

using level1<DataflowOutputClass<T>>::getOutputImpl;

//overload for when N = 0
std::unique_ptr<double> getOutputImpl(
PortIdxType<0>, crPolyIndex c_Idx
) const;

// ...
};

В настоящее время требуется объявление об использовании, так как метапрограммирование OP должно создавать допустимый тип возврата для выражения getOutput<N>(PolyIndex({})), Текущий подход отличает void из не-void возвращаемые типы. Вместо этого мы можем просто определить, является ли выражение getOutput<N>(PolyIndex{}) хорошо сформирован. Для этого я буду использовать Уолтера Е. Брауна void_t техника:

template<typename T>
struct voider { using type = T; };

template<typename T>
using void_if_well_formed = typename voider<T>::type;

Мы будем использовать это следующим образом:

void_if_well_formed< decltype(expression) >

даст тип void если выражение правильно сформировано. В противном случае, если выражение неправильно сформировано из-за сбоя замещения в непосредственном контексте, весь void_if_well_formed<..> приведет к ошибке замещения в ближайшем контексте. Эти виды ошибок могут использоваться методом, называемым SFINAE: Ошибка замещения не является ошибкой. Это может быть более подходящим, чтобы быть названным Сбой замещения в непосредственном контексте не является ошибкой.

SFINAE могут быть использованы, например, объявив два функциональных шаблона. Позволять expression<T>() означает любое выражение, которое зависит от T,

template<typename T, void_if_well_formed<decltype(expression<T>())>* = nullptr>
std::true_type  test(std::nullptr_t);

template<typename T>
std::false_type test(void*);

Если теперь мы называем тест через test<some_type>(nullptr)первая перегрузка предпочтительна, потому что типы аргументов точно соответствуют типу параметра функции. Однако вторая перегрузка также жизнеспособна. Если первая перегрузка неправильно сформирована из-за SFINAE, она удаляется из набора перегрузок, и вместо нее выбирается вторая перегрузка:

template<typename T>
using test_result = decltype( test<T>(nullptr) );

Используя эти методы, мы можем реализовать level1 следующее:

template<typename TLeafType>
class level1
{
public:
template <Index N, typename T = TLeafType>
using output_t =
decltype(std::declval<T*>() ->
getOutputImpl(PortIdxType<N>{}, std::declval<crPolyIndex>()));

static constexpr Natural getOutputPortsNumber(void)
{return getOutputPortsNumberImpl<0>(nullptr);}

template<Index N>
static constexpr Index getOutputPortsNumberImpl(void*)
{return N;}

template<Index N, typename T = TLeafType,
void_if_well_formed<output_t<N, T>>* = nullptr>
static constexpr Index getOutputPortsNumberImpl(std::nullptr_t)
{return getOutputPortsNumberImpl<N + 1>(nullptr);}

};

С немного больше работы, мы можем даже написать это следующим образом:

template<Index N>
struct HasOutputFor
{
static auto P() -> PortIdxType<N>;
static auto cr() -> crPolyIndex;

template<typename T>
static auto requires_(T&& t) -> decltype(t.getOutputImpl(P(), cr()));
};

template<Index N, typename T = TLeafType, REQUIRE( HasOutputFor<N>(T) )>
static constexpr Index getOutputPortsNumberImpl(std::nullptr_t);
2

Другие решения


По вопросам рекламы [email protected]