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
, wherei
is the constant expression identifier index, should be the identifiers types, - for each identifier, the function
get<i>(c)
, wherei
is the constant expression identifier index, andc
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>>()));
};
}
Now this works nicely:
auto c = std::complex<int>{1,1};
auto [r, i] = c + 2;
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;
}
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);
Better not use it with a temporary though.
Top comments (2)
Your
if constexpr
needs theelse
otherwise the secondreturn
is always there. In this case, it's harmless, but it compiles in unreachable code that the compiler may warn about. The point ofif constexpr
is to not compile code on either theif
orelse
.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.