#c #c 20 #c -concepts
Вопрос:
Я хотел бы иметь хороший способ включить функциональность(например, ,*=,/) моих сильных типов(например, StockPrice, Count).
Мне не нравится использовать для этого наследование (CRTP/миксины), я понимаю, что некоторым это может понравиться, но я предпочитаю не использовать наследование для этого случая использования.
Поэтому у меня есть такой код:
template<class, template<class...> class>
inline constexpr bool is_specialization = false;
template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;
template<typename Underlying, typename TagType>
struct StrongT{
Underlying val;
using Tag = TagType;
};
template<typename T>
concept addable = is_specialization<T, StrongT> amp;amp; requires (Tamp; t){
typename T::Tag::addable_trait;
};
struct IndexTag{
using addable_trait = int;
};
using Index = StrongT<size_t, IndexTag>;
struct NanosTag{
using addable_trait = int;
};
using Nanos = StrongT<size_t, struct NanosTag>;
template<addable T>
T operator (const Tamp; a, const Tamp; b){
return T{a.val b.val};
}
Мне это нравится, так как я могу перечислить свои «черты» в структуре тегов, но мне это не нравится, так как это немного спам. Я не могу объявить структуру тегов встроенной, как это:
using Nanos = StrongT<size_t, struct NanosTag{/*...*/}>;
Итак, есть ли способ сделать то, что я хочу, более коротким способом, без использования наследования?
примечание: У меня здесь только одна черта/концепция, очевидно, я хочу, чтобы поддерживалось много черт, например, сопоставимых/увеличиваемых…
Комментарии:
1. Вы хотите избежать всего наследования, везде? Или было бы нормально составить теги признаков с наследованием?
Ответ №1:
Как насчет чего-нибудь вроде
// Different capability tags
struct Addable{};
struct PreIncrementable{};
// ...
template <typename Underlying,
typename TagType,
typename... CapabilityTags>
struct StrongT
{
Underlying val;
using Tag = TagType;
friend StrongT operator (const StrongTamp; lhs, const StrongTamp; rhs)
requires((std::is_same_v<Addable, CapabilityTags> || ...))
{
return T{lhs.val rhs.val};
}
StrongTamp; operator ()
requires((std::is_same_v<PreIncrementable, CapabilityTags> || ...))
{
val;
return *this;
}
//...
};
и затем
struct NanosTag;
using Nanos = StrongT<size_t, NanosTag, Addable /*, ..*/>;
Комментарии:
1. Не уверен, что
TagType
это все еще нужно, но, возможно, у него были и другие цели.2. @aschepler:
TagType
это для «имени».3. Если мы хотим определить функции , не являющиеся членами , за пределами определения
StrongT
, мы могли быStrongT
добавить общедоступныйusing capabilities = std::tuple<CapabilityTags...>;
, и другие объявления могут ограничить использование этого. И/илиtemplate <typename Tag> static constexpr bool supports = (std::is_same_v<Tag, CapabilityTags> || ...);
4. @aschepler Тип тега делает тип уникальным. Без этого два разных сильных типа, которые были int и Addable, на самом деле были бы одним и тем же типом.
5. @Jarod42, смысл сильных типов заключается в повышении безопасности кода, и в объявлении тегов внутри типов шаблонов есть определенный недостаток, который может изменить значение вашего сильного типа, чтобы он был НЕ таким сильным, как вы думаете. Это также может привести к неопределенному поведению. Код, добавляемый в заголовок, который вы прямо (или косвенно) можете нарушить своим сильным типом тонкими и удивительными способами. youtu.be/26-7V8M1TmQ Бесстыдная вилка. 🙂
Ответ №2:
Если я вас правильно понимаю, то, что вы ищете, — это способ убедиться и даже обеспечить программное обеспечение того, чтобы класс реализовывал некоторые функции без наследования. Один из возможных способов сделать это-создать concept
класс, для которого требуется эта функциональность, а затем использовать static_assert
для проверки того, что данный класс предоставляет эту функциональность.
Пример: Допустим, нам нужен класс
с --
операторами и, нам нужно будет объявить концепцию, подобную этой:
template<typename T>
concept ExpectedTraits = requires(T t) {
t ;
t--;
};
Затем, если мы хотим утверждать, что (например) класс Object
обладает желаемыми чертами, нам нужно это утверждать —
static_assert(ExpectedTraits<Object>);
Если класс Object
не поддерживает
и --
, это утверждение завершится ошибкой, и код не будет компилироваться до Object
тех пор, пока не будет поддерживаться этими операндами.