The std::tuple
template calss is a powerful tool since it's a generalization of std::pair
. In particular, it defines a fixed-size collection of heterogeneous values [1].
The greatest benefitsprovided by std::tuple
are:
- Wrapping data
- Restricting operations
- Achiving more compact code
A simple use case
Let us assume that we have inherited the following code:
#include <tuple>
// [...]
void run(const Trajectory &i_trajectory) {
// [...]
// 1. get trajectory params
const int numberOfPoints = static_cast<int>(orbitMap.size());
const Trajectory::Time startTime = orbitMap.cbegin()->first;
const Trajectory::Time endTime = orbitMap.crbegin()->first;
// [...]
// 2. perform interpolation
Trajectory interpTrajectory = buildInterpolatedTrajectory(
interpOrder, interpDeltaTime, numberOfPoints, startTime, endTime);
// [...]
// 3. get trajectory params again
const int interpNumberOfPoints = static_cast<int>(interpTrajectory.size());
const Trajectory::Time interpStartTime = interpTrajectory.cbegin()->first;
const Trajectory::Time interpEndTime = interpTrajectory.crbegin()->first;
MachineTrajectoryInfo machineTrajectoryInfo;
// 4. pass the trajectory params to another object
machineTrajectoryInfo.setNumberOfSamples(numberOfPoints);
machineTrajectoryInfo.setStartTime(startTime);
machineTrajectoryInfo.setEndTime(endTime);
// [...]
}
where the Trajectory
class and the buildInterpolatedTrajectory
are definited as follow:
#include <array>
#include <map>
class Trajectory {
public:
struct PointInfo {
PointInfo(const std::array<double, 3> i_position,
const std::array<double, 3> i_velocity);
std::array<double, 3> position;
std::array<double, 3> velocity;
};
using Time = double;
using DataPoint = std::pair<Time, PointInfo>;
using TrajectoryMap = std::map<Time, PointInfo>;
Trajectory() = default;
Trajectory(const TrajectoryMap &i_trajectoryMap);
void addPoint(const DataPoint &i_trajectoryPoint);
const TrajectoryMap &getTrajectoryMap() const;
private:
TrajectoryMap m_trajectoryMap;
};
Trajectory buildInterpolatedTrajectory(int interpOrder, double interpDeltaTime,
int numberOfPoints, double startTime,
double endTime);
In addition, we know that
- the
run
function is a legacy fucntion with a multiple operations, and multiple lines of code (of course it is not a good practice have big functions, but legacy code can be infamous). - the operations 1,2,3, and 4 are not the core operations of the function, but they are necessary for the flow of the program.
We want to improve the code readability and the flow of the program, wrapping toghether the trajectory parameters (numberOfPoints
, startTime
, endTime
). One option is to wrap the Trajectory params in a data structure.
Starting form C++11 we can introduce in our code the std::tuple
to wrap heterogeneous data.
#include <tuple>
// [...]
void run(const Trajectory &i_trajectory) {
// [...]
// A. enum to improve code readability
enum ETrajectoryParams {
eTrajectoryParams_NumberOfPoints = 0,
eTrajectoryParams_StartTime,
eTrajectoryParams_EndTime
};
// B. define a type alias for the tuple
using TrajectoryParams = std::tuple<are_core::UInt32, are_core::datation::Mjd,
are_core::datation::Mjd>;
// C. Introduce a lambda function that retrieve the trajectory parameters
const auto getTrajectoryParams =
[](const Trajectory &i_trajectory) -> TrajectoryParams {
const Trajectory::TrajectoryMap &trajectoryMap =
i_trajectory.getTrajectoryMap();
const int numberOfPoints = static_cast<int>(trajectoryMap.size());
const Trajectory::Time startTime = trajectoryMap.cbegin()->first;
const Trajectory::Time endTIme = trajectoryMap.crbegin()->first;
return {numberOfPoints, startTime, startTime};
};
// 1. get trajectory params
TrajectoryParams trajectoryParams getTrajectoryParams(i_trajectory);
//[...]
// 2. perform interpolation
Trajectory interpTrajectory = computeInterpolation(
interpOrder, interpDeltaTime, numberOfPoints, startTime, endTime);
// [...]
// 3. get trajectory params again
TrajectoryParams interpTrajectoryParams getTrajectoryParams(i_trajectory);
// 4. pass the trajectory params to another object
MachineTrajectoryInfo machineTrajectoryInfo;
machineTrajectoryInfo.setNumberOfSamples(numberOfPoints);
machineTrajectoryInfo.setStartTime(startTime);
machineTrajectoryInfo.setEndTime(endTime);
// [...]
}
In order to introduce the std::tuple
, we have added two important entities:
- A local enum to make easier the params retrivial and more expressive.
- A local type alias to make the code more meaningful.
These two entities are very useful if you work in a big codebase shared with other developers. Indeed, their help to understand the meaning of the semantic flow of the program explicitating the intentions with the appropriate names.
The last added entity is:
- A local lambda function to avoid duplicated operation.
All these three entities are definited locally since we don't want to expose these litte details in another.
Alternative solution
A common alternative solution can be use a POD struct. But it is a more powerful tool since a beginner developer can extend the struct with methods, violating two design principles:
- Single responsability principle: use the data wrapper only for a precise intent: store data.
- Open-close principle: restrict the data wrapper to only store data. Add no more features.
Although a POD class can be implemented to solve the prevous problem, it is a more powerful tool. Indeed, a struct offer more extension for example a beginner programmer can think to add a method to this struct vaiolating the single resposability principle.
Final Thoughts
In my huble opinion, in general, I prefer to use:
- The
std::tuple
complemented with anenum
and an alias to make the code more readable. Moreover, I prefer to implement them within functions, as a detailed structure; in factenum
andstd::tuple
are not a single entity, one risks using them separately, losing the meaning for which they were implemented. - The POD
struct
in all other cases.
Be awere that between std::tuple
and struct
s there are a few implementation details which can became critical in some situations. See the the article here about POD struct vs tuple.
References
[1]: The std::tuple definition https://en.cppreference.com/w/cpp/utility/tuple