Реализация функций для сильных типов с концепциями C

#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 тех пор, пока не будет поддерживаться этими операндами.