Стиль шаблонов программирования

#c #templates

#c #шаблоны

Вопрос:

Если бы у вас был вариант, какой бы вы выбрали?

 template<class CheckEveryNode<true>>
struct X;

or 

template<bool CheckEveryNode>
struct X;
  

Это не совсем очевидно с точки зрения дизайнеров, с одной стороны, у нас есть читаемость в реальном коде:

 //taking first approach
//somewhere in the code
X<CheckEveryNode<false>> x;
  

на второй стороне можно напечатать гораздо больше, и кто-то предпочел бы:

 //taking second approach
//somewhere in the code
X<false> x;//but here we don't really see immediately what the false means.  
  

Итак, с нетерпением ждем ваших мнений / предложений

Ответ №1:

Часто занимаясь метапрограммированием, я могу порекомендовать только «подробный» подход, но мне не совсем нравится первый подход, который вы предлагаете.

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

Например:

 struct NodeCheckerTag {};

struct CheckEveryNode {
  typedef NodeCheckerTag PolicyTag;
  void check(List constamp; list);
};

struct CheckFirstNode {
  typedef NodeCheckerTag PolicyTag;
  void check(List constamp; list);
};

template <typename Rand>
struct CheckSomeNodes {
  typedef NodeCheckerTag PolicyTag;
  CheckSomeNodes(Rand rand): _rand(rand) {}
  void check(List constamp; list);
  Rand _rand;
};
  

Таким образом, ваш класс должен позволять пользователю выбирать, какую политику выбрать:

 template <typename NodeChecker>
class X: NodeChecker // allow stateful implementation but let EBO kick in
{
};
  

PolicyTag Это связано с наличием нескольких политик:

 template <typename NodeChecker, typename NodeAllocator, typename NodeNotifier>
class X;
  

Обычно вы должны предоставлять разумные значения по умолчанию, но тогда всегда есть случай, когда я хочу настроить только последнее! переключившись на шаблон variadic, вы можете получить это:

 template <typename Tag, typename Default, typename... Policies>
struct PolicySelector
{
  typedef /**/ type;
};

template <typename... Policies>
class X: Policies...
{
  typedef typename PolicySelector<NodeCheckerTag, CheckNoNode,
    Policies...>::type NodeCheckerPolicy;
  typedef typename PolicySelector<NodeAllocatorTag, StdAllocator,
    Policies...>::type NodeAllocatorPolicy;
  ...
};
  

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

Комментарии:

1. @Natthieu Благодарю вас за (уточняющий пример). Единственное, о чем я спрашивал здесь, это каким способом это следует делать — с помощью политик, даже если они могут быть громоздкими в написании, или с помощью голых «встроенных модулей». Больше ничего. Но спасибо вам за ваш (такой подробный) ответ.

Ответ №2:

Первый

 template<class CheckEveryNode<true>>
struct X;
  

Было бы неправильно, это должно быть

 template<template<bool> CheckEveryNode>
struct X{};
  

для чего вам потребуется либо специализироваться на условии true, либо false:

 // above is true case, below is false case
template<>
struct X<CheckEveryNode<false> >{};
  

Или используйте частичную специализацию следующим образом:

 template<class CheckEveryNode>
struct X;

template<bool B>
struct X<CheckEveryNode<B> >{
  // real implementation
};
  

Помимо этого, второй вариант проще реализовать, а также его легче читать, потому что он «как ожидалось». Когда вы предоставляете аргумент шаблона, вы знаете, для чего он нужен, нет необходимости в дополнительной структуре.

Ответ №3:

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

 struct CheckEvery { };
struct CheckNone { };

template<typename Check> struct Thingy;
template<> Thingy<CheckEvery> { ... };
template<> Thingy<CheckNone> { ... };
  

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

 struct CheckEvery { bool shouldCheck(int) { return true; } };
struct CheckNone { bool shouldCheck(int) { return false; } };
struct CheckOdds { bool shouldCheck(int i) { return i % 2; } };

template<typename Checker> struct Thingy {
  Checker checker;
  void someFunction(int i) { if(checker.shouldCheck(i)) check(i); }
};
  

Пример не очень хорошо проработан, но он дает вам представление. Вы, вероятно, захотите предоставить Checker объект Thingy конструктору, например.

Комментарии:

1. спасибо за ваш ответ, но я опубликовал пример с логическими значениями просто в качестве «примера». Здесь меня интересует мнение пользователей о том, как они воспринимают политики в сравнении с «голыми» типами.

2. Вы просили предложения, это мои.

Ответ №4:

Второй вариант мне тоже нравится. Моя философия заключалась бы в следующем: зачем создавать дополнительный тип, если в этом нет необходимости?

Ответ №5:

Первый подход выглядит громоздким. Я бы выбрал второй вариант в отсутствие убедительного обоснования первого. Что касается удобочитаемости, то второй вариант удобочитаем. Фактически, первый из них снижает удобочитаемость из-за его шаблонного синтаксиса.

Комментарии:

1. @Nawaz имейте в виду, что будет / возможно, более одной «политики», и тогда, если вы выберете первую, у вас будет X<false, true, false, false, true>, и это на самом деле не так очевидно для более сложных, но очень читабельно. И старая максима гласит: «Компьютерные программы читаются компьютерами всего несколько раз, но людьми сотнями, если не тысячами».

2. @Мы ничего не можем поделать: Итак, скажите мне, что вы находите более читаемым: X<false,true,false,false,true> ИЛИ X<A_type<false>,B_type<true>,C_type<false>,D_type<false>,E_type<true>> ?

3. @Nawaz вы знаете, что существует также искусство именования типов.

4. @Мы ничего не можем поделать: я не говорю об именах типов, скорее просто представьте, что вы видите так много типов внутри типа в качестве аргументов типа для него, и каждый аргумент типа по очереди имеет либо true , либо false в качестве аргумента значения. Это, конечно, выглядит громоздко. Это выглядит точно так же, как когда компилятор генерирует ошибку для кода шаблона. Вы действительно хотите видеть их при написании кода? Вы действительно находите его читабельным?

5. @Nawaz а кто сказал, что всегда должно быть только true или false (bool)?