The preamble: Hey, let's say I have various representations of data that I want to convert seamlessly in-between in an any-to-any way. The representations are out of my control. My practical example is the object orientation in 3D: we have Quaternions, Euler angles, Angle-Axis, and Rotational matrices, everything in various classes from different libraries (which I have to use). For this, I built a proxy class which stores the value in one particular representation and can convert into it by overloaded constructors and out of it by overloaded typecast operators, just like this:
Eigen::Quaterniond eig_quaternion = AttitudeConvertor(roll, pitch, yaw);
tf2::Quaternion tf2_quaternion = AttitudeConvertor(eig_quaternion);
The problem: So far, so good until I want to overload the typecast to std::tuple, which is handy, e.g., when returning the yaw, pitch and roll angles, which would look like this:
auto [roll2, pitch2, yaw2] = AttitudeConvertor(tf2_quaternion);
The class can be compiled, but the assignments to auto [a, b, c] and std::tie(a, b, c) do not work. A workaround can be made in the form of a dedicated function, which returns the tuple. Or by creating a custom class just for storing the three doubles. These work just fine, but it is not that seamless anymore.
I know that functions can not be overloaded by their return type. That is the reason why I created this proxy class. But is there any other way how the tuple could be returned? Even if it is for just a single variant of the tuple? Or should I approach this problem differently?
I prepared a minimum (non)working example in the theme of simpler number conversion:
#include <iostream>
#include <math.h>
#include <tuple>
using namespace std;
class NumberConvertor {
public:
// | ---------------------- constructors ---------------------- |
NumberConvertor(const int& in) {
value_ = double(in);
}
NumberConvertor(const double& in) : value_(in){};
// | ------------------- typecast operators ------------------- |
operator int() const {
return int(value_);
}
operator double() const {
return value_;
}
// return the integer and the fractional part
operator std::tuple<int, double>() const {
int int_part = floor(value_);
double frac_part = fmod(value_, int_part);
return std::tuple(int_part, frac_part);
}
// | ------------------------ functions ----------------------- |
// the workaround
std::tuple<int, double> getIntFrac(void) const {
int int_part = floor(value_);
double frac_part = fmod(value_, int_part);
return std::tuple(int_part, frac_part);
}
private:
double value_; // the internally stored value in the 'universal representation'
};
int main(int argc, char** argv) {
// this works just fine
int intval = NumberConvertor(3.14);
double fracval = NumberConvertor(intval);
cout << "intval: " << intval << ", fracval: " << fracval << endl;
// this does not compile
// auto [int_part, frac_part] = NumberConvertor(3.14);
// neither does this
// int a;
// double b;
// std::tie(a, b) = NumberConvertor(3.14);
// the workaround
auto [int_part2, frac_part2] = NumberConvertor(3.14).getIntFrac();
cout << "decimal and fractional parts: " << int_part2 << ", " << frac_part2 << endl;
std::tie(int_part2, frac_part2) = NumberConvertor(1.618).getIntFrac();
cout << "decimal and fractional parts: " << int_part2 << ", " << frac_part2 << endl;
return 0;
};
Makefile:
main: main.cpp
g++ -std=c++17 main.cpp -o main
all: main
Expected output:
intval: 3, fracval: 3
decimal and fractional parts: 3, 0.14
decimal and fractional parts: 1, 0.618
Jarod42s hint to binding_a_tuple-like_type made me come up with the following.
I basically make your NumberConvertor act like a tuple.
using as_tuple_type = std::tuple<int,double>;
For convenience an alias template can be used:
template <size_t i>
using nth_type = typename std::tuple_element_t<i,as_tuple_type>;
Using that, we can provide a get method:
struct NumberConvertor {
NumberConvertor(const int& in) : value_(in) {}
NumberConvertor(const double& in) : value_(in) {};
template <size_t i> nth_type<i> get();
private:
double value_;
};
template <> nth_type<0> NumberConvertor::get<0>() { return value_;}
template <> nth_type<1> NumberConvertor::get<1>() { return value_;}
The specializations aren't really needed here, but I suppose for the real scenario this is not the case.
Finally we provide specializations for std::tuple_size and std::tuple_element:
template <>
struct std::tuple_size<NumberConvertor> : std::tuple_size<as_tuple_type>
{};
template <size_t i>
struct std::tuple_element<i,NumberConvertor> : std::tuple_element<i,as_tuple_type>
{};
Now this will work:
int main(int argc, char** argv) {
auto [int_part, frac_part] = NumberConvertor(3.14);
std::cout << int_part << " " << frac_part;
};
Complete example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With