DEV Community

panmanio
panmanio

Posted on • Originally published at burnicki.pl

Enabling structured binding for std::complex (and custom non-POD types)

The father of C++, Bjarne Stroustrup, mentions in A Tour of C++ that it's possible to use structured binding declaration with std::complex, which is a non-POD type (it encapsulates the data attributes, values are obtained with real() and imag() method calls). As of today it actually does not work for std::complex (checked with the Compiler Explorer), also the WG21 standard working draft does not mention that ability of std::complex. But it is possible to make it work by making std::complex a tuple-like type by incorporating these rules (see structured binding):

  • std::tuple_size<std::complex<T>>::value should be an integer constant expression denoting the number of identifiers that are part of the structured binding,
  • for each identifier an expression std::tuple_element<i, std::complex<T>>::type, where i is the constant expression identifier index, should be the identifiers types,
  • for each identifier, the function get<i>(c), where i is the constant expression identifier index, and c is the complex object, should provide the value of the identifiers.

We simply need to extend the std namespace and provide all ingredients.

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>>()));
    };
}
Enter fullscreen mode Exit fullscreen mode

Now this works nicely:

auto c = std::complex<int>{1,1};
auto [r, i] = c + 2;
Enter fullscreen mode Exit fullscreen mode

Similarly for a custom type:

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;
}
Enter fullscreen mode Exit fullscreen mode

In our own types we can define a member get<i>() method instead of standalone get<i>(obj).

Side note: there is an interesting property of std::complex<T>:

4
If z is an lvalue of type cv complex then:

(4.1)
the expression reinterpret_cast(z) is well-formed,

(4.2)
reinterpret_cast(z)[0] designates the real part of z, and

(4.3)
reinterpret_cast(z)[1] designates the imaginary part of z.

That means we could simply cast:

auto c = std::complex<int>{1,1};
auto [r, i] = reinterpret_cast<int(&)[2]>(c);
Enter fullscreen mode Exit fullscreen mode

Better not use it with a temporary though.

Top comments (2)

Collapse
 
pauljlucas profile image
Paul J. Lucas

Your if constexpr needs the else otherwise the second return is always there. In this case, it's harmless, but it compiles in unreachable code that the compiler may warn about. The point of if constexpr is to not compile code on either the if or else.

Collapse
 
maniowy profile image
panmanio

You're correct, thank you for catching it, it's fixed now. I was mislead by the fact that I am returning there, which would be OK for a normal if, but yes, there should be an else in order not to compile that part of code that does not go into the true branch.