среда, 4 июня 2025 г.

[prog.c++] Осваиваю C++ные концепты: одна или две константны внутри типа

Недавно довелось столкнутся с задачкой, в которой пользователь должен определить тип с перечнем свойств. Что-то вроде traits из нашего проекта RESTinio. При этом внутри такого типа обязательно должна быть константа high_watermark типа std::size_t. Например:

struct my_traits {
  ... // Какие-то определения типов.

  // Максимальный размер при достижении которого нужно провести чистку данных.
  static constexpr std::size_t high_watermark = 64 * 1024 * 1024;
};

А кроме этого может быть определена и вторая константа, low_watermark. Т.е. класс свойств может выглядеть и так:

struct my_traits {
  ... // Какие-то определения типов.

  // Максимальный размер при достижении которого нужно провести чистку данных.
  static constexpr std::size_t high_watermark = 64 * 1024 * 1024;

  // Размер при достижении которого чистку данных следует остановить.
  static constexpr std::size_t low_watermark = 16 * 1024 * 1024;
};

При использовании таких типов свойств нужно было как-то понять, есть ли в свойствах константа low_watermark. Если есть, то нужно было использовать именно ее значение. А если нет, то оставалось высчитывать нижний порог из high_watermark.

Попробовал применить для этой цели концепты, благо в проекте C++20. Получилось так, как показано под катом.

Это лучшее, что удалось придумать. К сожалению, в возможностях C++20 пока еще не сильно копенгаген, только учусь. Поэтому интересно, а можно ли было сделать лаконичнее и понятнее?

Полный код под катом, а проверить его можно, например, на Wandbox.

Upd. Под катом так же и второй вариант, более компактный за счет отсутствия одного из концептов и использования if constexpr внутри вспомогательной функции low_watermark.

Upd. Более безопасно использовать std::same_as вместо std::is_same_v.

Первый, исходный вариант:

#include <iostream>
#include <type_traits>

template<typename T>
concept has_high_watermark =
    std::is_same_v<decltype(T::high_watermark), const std::size_t>;

template<typename T>
concept has_low_watermark =
    std::is_same_v<decltype(T::low_watermark), const std::size_t>;

template<typename T>
concept has_high_and_low_watermarks =
    has_high_watermark<T> && has_low_watermark<T>;

struct with_high_watermark_only {
    static constexpr std::size_t high_watermark = 10240;
};
struct with_both_watermarks {
    static constexpr std::size_t high_watermark = 10240;
    static constexpr std::size_t low_watermark = 8000;
};

template<has_high_and_low_watermarks T>
std::size_t
low_watermark()
{
    static_assert(T::high_watermark > T::low_watermark);
    return T::low_watermark;
}

template<has_high_watermark T>
std::size_t
low_watermark()
{
    return (T::high_watermark / 3u) * 2u;
}

int main()
{
    std::cout << "with_high_watermark_only: "
        << low_watermark<with_high_watermark_only>()
        << std::endl;
    std::cout << "with_both_watermarks: "
        << low_watermark<with_both_watermarks>()
        << std::endl;
}

Второй, более компактный.

#include <iostream>
#include <type_traits>

template<typename T>
concept has_high_watermark =
    std::same_as<decltype(T::high_watermark), const std::size_t>;

template<typename T>
concept has_low_watermark =
    std::same_as<decltype(T::low_watermark), const std::size_t>;

struct with_high_watermark_only {
    static constexpr std::size_t high_watermark = 10240;
};
struct with_both_watermarks {
    static constexpr std::size_t high_watermark = 10240;
    static constexpr std::size_t low_watermark = 8000;
};

template<has_high_watermark T>
std::size_t
low_watermark()
{
    if constexpr(has_low_watermark<T>)
    {
        static_assert(T::high_watermark > T::low_watermark);
        return T::low_watermark;
    }
    else
        return (T::high_watermark / 3u) * 2u;
}

int main()
{
    std::cout << "with_high_watermark_only: "
        << low_watermark<with_high_watermark_only>()
        << std::endl;
    std::cout << "with_both_watermarks: "
        << low_watermark<with_both_watermarks>()
        << std::endl;
}

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