Включение структурированного связывания для std::complex (и пользовательских не-POD типов)

Отец C++, Бьярне Строуструп, упоминает в A Tour of C++, что можно использовать объявление структурированного связывания с std::complex, который является не-POD типом (он инкапсулирует атрибуты данных, значения получаются с помощью вызовов методов real() и imag()). На сегодняшний день это фактически не работает для std::complex (проверено с помощью Compiler Explorer), также в рабочем проекте стандарта WG21 не упоминается эта возможность std::complex. Но это возможно, если сделать std::complex кортежеподобным типом, включив такие правила (см. структурированное связывание):

  • для каждого идентификатора выражение std::tuple_element<i, std::complex<T>>::type, где i — индекс идентификатора константного выражения, должно быть типами идентификаторов,
  • для каждого идентификатора функция get<i>(c), где i — индекс идентификатора постоянного выражения, а c — сложный объект, должна предоставлять значение идентификаторов.

Нам просто нужно расширить пространство имен std и предоставить все ингредиенты.

namespace std {
    template<typename T>
    class tuple_size<complex<T>> {
    public:
        static constexpr size_t value = 2;
    };
    template<size_t I, typename T>
    auto get(const complex<T>& c) {
        if constexpr (I == 0) return c.real();
        else return c.imag();
    }
    template <size_t I, typename T>
    class tuple_element<I, complex<T>> {
    public:
        using type = decltype(get<I>(declval<complex<T>>()));
    };
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь это хорошо работает:

auto c = std::complex<int>{1,1};
auto [r, i] = c + 2;
Войти в полноэкранный режим Выход из полноэкранного режима

Аналогично для пользовательского типа:

template <typename X, typename Y>
class NonPOD {
    X x;
    Y y;
public:
    NonPOD(X x, Y y): x{x}, y{y} {}
    X getX() const { return x; }
    Y getY() const { return y; }
};

namespace std {
template <size_t I, typename Arg, typename ...Args>
class type_alternatives {
public:
    using type = typename type_alternatives<I-1, Args...>::type;
};

template <typename Arg, typename ...Args>
class type_alternatives<0, Arg, Args...> {
public:
    using type = Arg;
};

template <size_t I, typename X, typename Y>
class tuple_element<I, NonPOD<X, Y>> {
public:
    using type = typename type_alternatives<I, X, Y>::type;
};
} // namespace std

template <size_t I, typename X, typename Y>
typename std::tuple_element<I, NonPOD<X, Y>>::type
get(const NonPOD<X, Y>& t) {
    if constexpr (I == 0) return t.getX();
    else return t.getY();
}

auto test() {
 auto sb = NonPOD{1, 2.};
 auto [s, b] = sb;
 return s+b;
}
Войти в полноэкранный режим Выход из полноэкранного режима

В наших собственных типах мы можем определить метод-член get<i>() вместо отдельного get<i>(obj).

Побочное замечание: существует интересное свойство std::complex<T>:

4
Если z — l-значение типа cv complex, то:

(4.1)
выражение reinterpret_cast(z) является хорошо сформированным,

(4.2)
reinterpret_cast(z)[0] обозначает вещественную часть z, и

(4.3)
reinterpret_cast(z)[1] обозначает мнимую часть z.

Это означает, что мы можем просто выполнить кастинг:

auto c = std::complex<int>{1,1};
auto [r, i] = reinterpret_cast<int(&)[2]>(c);
Войти в полноэкранный режим Выйти из полноэкранного режима

Однако лучше не использовать его с временными параметрами.

Оцените статью
devanswers.ru
Добавить комментарий