Небольшой мемориз. Пока не дошло до мышечной памяти.
Естественно - через концепты.
Свой концепт
Самый простой вариант: объявить концепт на проверку нужных полей:
#include <concepts>
template<typename T>
concept has_member_variables = requires (T t) {
{ auto(t.f) } -> std::floating_point;
{ auto(t.i) } -> std::integral;
};
struct foo { float f; int i; };
struct bar { float f; };
struct baz { float i; int z; };
static_assert(has_member_variables<foo>);
static_assert(!has_member_variables<bar>);
static_assert(!has_member_variables<baz>);
Может статься полезным на всякие ограничения на классы. Погонять: https://godbolt.org/z/WsYx7P4b9
Ветвления в коде
Понятно, что не runtime, а compiletime. Может оказаться полезным для какого-то опционального поведения: к примеру: если класс представляет какое-то поле - то использовать его значение, в противном случае - какой-то дефолт.
Выглядеть это может как-то так:
if constexpr (requires (T t) { { auto(t.g) } -> std::floating_point; }) {
return val.g;
}
Полный пример ( https://godbolt.org/z/zsjbrnxoT):
#include <concepts>
#include <print>
struct Earth {};
struct Moon {float g{1.625f}; };
template<typename T>
float get_g(const T& val)
{
if constexpr (requires (T t) { { auto(t.g) } -> std::floating_point; }) {
return val.g;
}
return 9.82f;
}
int main()
{
std::println("Earth g={}", get_g(Earth{}));
std::println("Moon g={}", get_g(Moon{}));
}
“Колбеки”
Это уже не про поля в коде. Речь про выполнение каких-то функций или методов, если они предоставлены пользователем. К примеру, при использовании CRTM: можно предоставить “колбеки”, on_start(), on_stop(), on_error() и так далее и если они реализованы, то вызывать этот код, в противном случае - нет.
Вызов будет очень похожим на проверку поля, только не будет auto(EXPR):
if constexpr (requires (T t) { { t.get_g() } -> std::floating_point; }) {
return val.get_g();
}
И полный пример ( https://godbolt.org/z/8xrWKoTsr):
#include <concepts>
#include <print>
struct Earth {};
struct Moon {
constexpr float get_g() const noexcept {
return 1.625f;
}
};
template<typename T>
float constexpr get_g(const T& val)
{
if constexpr (requires (T t) { { t.get_g() } -> std::floating_point; }) {
return val.get_g();
}
return 9.82f;
}
int main()
{
std::println("Earth g={}", get_g(Earth{}));
std::println("Moon g={}", get_g(Moon{}));
}
Если же тип возвращаемого значения вообще не имеет значения, то проверка упростится до:
if constexpr (requires (T t) { t.do_process(); }) {
val.do_process();
}