Let's say that types such as int
and float
are specific, and types such as the proxy object shown in the linked question are generic. Our options are to be specific to begin with, in which case we just coast through the rest, or we give rise to a generic type and handle all the various specific types we may support.
The proxy object shown in the linked question is an example of a variant type, and boost::variant
is a generic implementation of this. For example, boost::variant<int, float>
allows us to hold either int
or float
.
My recommendation really depends what you want. Do you
- want to specify the type you expect to get from the user and throw on unexpectd input? (specific to begin with and coast) OR,
- want to give rise to a different type depending on what the user inputted and specify a set of types you can handle? (Give rise to a generic type and handle the various specific types)
Specifying the type you expect from the user
In this case we can simply make the function templated and we specify the type we expect through the template parameter.
The example shown is kept totally generic but you can restrain template parameters using various techniques. Check out my answer regarding this topic.
#include <iostream>
/* Read data of type T from an input stream. */
template <typename T>
T read(std::istream &strm) {
T val;
strm >> val;
if (!strm) {
throw /* something */;
} // if
return val;
}
/* Print data of type T. */
template <typename T>
void print(const T &val) {
std::cout << val;
}
int main() {
print(read<int>(std::cin));
}
This will give rise to an int
for input such as 1
and even for input such as 1.
, 1.0
and 1.2
.
Handling different types you may get from the user
In this case we're actually lexing the input stream from the user. Our read
function will give rise to a generic type, boost::variant<int, float>
.
#include <iostream>
#include <boost/variant.hpp>
/* Naive implementation of a lexer. */
boost::variant<int, float> read(std::istream &strm) {
std::string lexeme;
strm >> lexeme;
try {
std::size_t idx;
auto val = std::stoi(lexeme, &idx);
if (idx == lexeme.size()) { // Make sure we converted the entire lexeme.
return val;
} // if
} catch (const std::exception &) {
// Do nothing. We'll try to lex it as float instead.
} // try
std::size_t idx;
auto val = std::stof(lexeme, &idx);
if (idx == lexeme.size()) { // Make sure we converted the entire lexeme.
return val;
} // if
throw /* something */;
}
/* Print the type and the value, to check that we have the correct type. */
void print(const boost::variant<int, float> &val) {
class visitor : public boost::static_visitor<void> {
public:
void operator()(int that) const {
std::cout << "int: " << that << std::endl;
}
void operator()(float that) const {
std::cout << "float: " << that << std::endl;
}
}; // visitor
boost::apply_visitor(visitor(), val);
}
int main() {
print(read(std::cin));
}
This approach will give rise to int
for input such as 1
, and give rise to float
for input such as 1.
, 1.0
as 1.2
.
As you can see, we give rise to a generic type, boost::variant<int, float>
, and handle the various specific types, int
and float
, in the visitor.