В приветственном примере дизайна, основанного на политике, из википедии у нас есть очень хороший способ определения конкретных функций-членов реализации (политики) с помощью директив using
:
template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy {
public:
// Behavior method.
void Run() const {
// Two policy methods.
Print(Message());
}
private:
using LanguagePolicy::Message;
using OutputPolicy::Print;
};
Теперь предположим, что вы хотите, чтобы пользователь забыл реализовать метод Message
в языковой политике — может быть много других, которые пользователь реализовал. Следовательно, вы определяете резервный класс
class DefaultLanguagePolicy {
protected:
std::string Message() const { return ""; }
// and fallbacks for other methods a user may not have implemented
}
Но как включить это значение по умолчанию, если Message
не реализовано в классе HelloWorld
, а в противном случае отбросить его?
template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy, private DefaultLanguagePolicy {
public:
// Behavior method.
void Run() const {
// Two policy methods.
Print(Message());
}
private:
// what do I need to write to get this kind of behaviour:
if (LanguagePolicy has Message implemented) {
using LanguagePolicy::Message;
} else {
using DefaultLanguagePolicy::Message;
}
using OutputPolicy::Print;
};
Спасибо за любые предложения, которые в идеале позволяют сохранить этот красивый синтаксис using
.
Вы можете установить для параметра шаблона значение по умолчанию, и класс будет использовать вашу DefaultLanguagePolicy, если пользователь не предоставил второй параметр шаблона. Соответствует ли это вашей цели?
DefaultLanguagePolicy должен быть определен перед классом HelloWorld.
class DefaultLanguagePolicy {
protected:
std::string Message() const { return "Please add a language policy\n"; }
};
template <typename OutputPolicy, typename LanguagePolicy = DefaultLanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy {
public:
void Run() const {
Print(Message());
}
Тестовый запуск напечатает «Пожалуйста, добавьте языковую политику», если языковая политика не указана.
int main() {
typedef HelloWorld<OutputPolicyWriteToCout> myDeafult;
myDeafult forgetLanguage;
forgetLanguage.Run();
HelloWorld<OutputPolicyWriteToCout, LanguagePolicyGerman> InGerman;
InGerman.Run();
}
Нет. Если указан второй параметр. Он будет использован. Пожалуйста, проверьте это.
Боюсь, пример слишком прост. Предположим, что LanguagePolicy
имеет более одного метода. Все, кроме Message
, реализованы, поэтому только Message
нужно заполнить значением по умолчанию. Установка параметра шаблона по умолчанию, как вы предлагаете, перезапишет все конкретные реализации LanguagePolicy
значениями по умолчанию, не так ли? Я отредактирую вопрос, чтобы сделать это более ясным.
@JReichardt Нет. Он не перезапишет вывод на другом языке. В последнем примере я использую имя объекта HellowWorld. Это вызывает проблемы в сочетании с именем класса. Я изменил имя переменной в версии eidted. Теперь это работает.
Я считаю, что мы неправильно понимаем нас. Насколько я понимаю, ваше решение обеспечивает значение по умолчанию для всей реализации LanguagePolicy
. Это все или ничего. Вместо этого я ищу способ предоставить значения по умолчанию только для определенных методов LanguagePolicy
, которые могут отсутствовать. Сам класс реализован - просто не со всеми функциями HelloWorld
хотелось бы его вызвать. Это проясняет ситуацию? В очередной раз благодарим за помощь.
Меня правильно поняли, и по умолчанию другие политики не перезаписываются. Просто запустите мой новый файл main(). И, конечно же, вы должны добавить другие политики, которые я не включил в свой пост.
Если в проекте можно установить проектное решение, что каждая политика вытекает из политики по умолчанию, например:
class SomeLangaugePolicy : public DefaultLanguagePolicy { ... };
Тогда в классе нечего менять HelloWorld
- я имею в виду первую версию этого класса.
В противном случае - используйте инструменты из <type_traits>
:
Определить, есть ли у данного класса функция Message, можно с помощью std::void_t
template <typename T, typename = std::void_t<>>
struct has_message : std::false_type {};
template <typename T>
struct has_message<T, std::void_t<decltype(&T::Message)>> : std::true_type {};
С этим трейтом has_message
, с std::conditional_t можно выбрать тип в зависимости от наличия Message
:
template <typename LanguagePolicy, typename DefaultLanguagePolicy>
using LanguageBase = std::conditional_t<has_message<LanguagePolicy>::value,
LanguagePolicy,
DefaultLanguagePolicy>;
И необходимые изменения в вашем классе:
template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
class HelloWorld :
private OutputPolicy,
private LanguageBase<LanguagePolicy, DefaultLanguagePolicy> {
public:
// Behavior method.
void Run() const {
// Two policy methods.
Print(Message());
}
private:
using LanguageBase<LanguagePolicy, DefaultLanguagePolicy>::Message;
using OutputPolicy::Print;
};
Рабочая Демо
Теперь то же самое легко сделать с OutputPolicy и всеми остальными политиками.
Вы можете определить макрос для определения детекторов политик:
#define DEFINE_HAS_FUNCTION_TRAIT(function) \
template <typename T, typename = std::void_t<>> \
struct has_##function : std::false_type {}; \
template <typename T> \
struct has_##function<T, std::void_t<decltype(&T::function)>> : std::true_type {}
Затем:
DEFINE_HAS_FUNCTION_TRAIT(Message); // define has_Message
DEFINE_HAS_FUNCTION_TRAIT(Print); // define has_Print
Чтобы обработать случай, когда в данной Политике больше функций, чем одна, используйте этот подход:
template <typename OutputPolicy, typename LanguagePolicy, typename DefaultLanguagePolicy>
class HelloWorld :
private OutputPolicy,
private LanguagePolicy, private DefaultLanguagePolicy {
public:
// Behavior method.
void Run() const {
// Two policy methods.
Print(Message());
}
private:
using std::conditional_t<has_Message<LanguagePolicy>::value,
LanguagePolicy,
DefaultLanguagePolicy>::Message;
using OutputPolicy::Print;
};
@PiortrNycz Большое спасибо. Это работает. Однако для этого требуется определить другую черту для наличия/отсутствия каждого метода в LanguagePolicy
. Может есть более простой способ? Можно с уверенностью предположить, что LanguagePolicy
реализует подмножество DefaultLanguagePolicy
, поэтому для каждого метода, отсутствующего в LanguagePolicy
, определенно есть один в DefaultLanguagePolicy
.
На самом деле, теперь, когда я думаю об этом больше, я вижу другую проблему: вы определяете LanguageBase
в зависимости от has_ message
— но разве это не непрактично, если в LanguagePolicy
есть другие функции, которые могут отсутствовать и нуждаться в значениях по умолчанию, верно?
Таким образом, с определением трейтов has_function
для каждой функции, которую LanguagePolicy
может реализовать, можно просто опустить определение LanguageBase
и поместить std::conditional
непосредственно в private:
части HelloWorld
— это именно то предложение if, которое я искал.
Вы можете определить макрос для обнаружения любой такой «функции». И, конечно, вы можете использовать conditional_t напрямую, однако, ИМХО, проще иметь такие вспомогательные определения типов, как LaungageBase.
Я предоставил решение для случаев, когда в одном классе политики можно обнаружить больше функций.
Великолепно! Это так приятно - спец. с макросом. Спасибо! Что касается предложения вывести LanguagePolicy из LanguagePolicyDefault, то это не вариант для меня, это тоже хорошо, но не вариант для меня. В моем случае по умолчанию нужны некоторые вещи (некоторые размерные настройки), которые доступны только из политики не по умолчанию.
Еще один аспект решения с макросами заключается в том, что это делает удобным определение has_function внутри класса, необходимого для проверки доступности закрытых функций-членов. Так что еще раз большое спасибо.
Спасибо за быстрый ответ. Это возможно, но, насколько я понимаю, все методы в классе LanguagePolicy будут перезаписаны значениями по умолчанию, не так ли? Я ищу способ вызывать значение по умолчанию только тогда, когда конкретный метод не реализован.