I want to write a function string_to_float with template parameter T such that string_to_float = std::stof, string_to_float = std::stod and string_to_float = std::stold, when T = float, T = double and T = long double, respectively. My attempt was the following:
template<typename T>
T string_to_float(std::string const& s, std::size_t* pos = nullptr)
{
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double> || std::is_same_v<T, long double>,
"T is not a built-in floating point type");
if constexpr (std::is_same_v<T, float>)
return std::stof(s, pos);
if constexpr (std::is_same_v<T, double>)
return std::stod(s, pos);
if constexpr (std::is_same_v<T, long double>)
return std::stold(s, pos);
return T{};
}
However, I worry about the return statement. While the static assertion will already fail in that case, I don't want to produce an additional misleading compiler error when T is not default constructible.
I also want to make sure that the code produced by an invocation of string_to_float is really exactly the same as if I had used std::stof, std::stod or std::stold directly (assuming, of course, that T = float, T = double or T = long double).
This is why I didn't remove the last if-clause checking for T being equal to long double and simply returned std::stold(s, pos); in the last line. On the other hand, at compile time the it is already clear if T = float or T = double and in that case, there would be a return before the return in the last line; so the compiler might ignore that return anyways.
I've also looked at the attribute specifier sequences and hoped that there is some kind of [[unreachable]] attribute so that the compiler really knows that the code below this line will never be reached.
I don't want to produce an additional misleading compiler error when
Tis not default constructible.
Then, don't include that last return T{};. It's not ever going to be used anyway.
Example:
template<class T>
inline constexpr bool always_false_v = false;
template <typename T>
T string_to_float(std::string const& s, std::size_t* pos = nullptr) {
if constexpr (std::is_same_v<T, float>)
return std::stof(s, pos);
else if constexpr (std::is_same_v<T, double>)
return std::stod(s, pos);
else if constexpr (std::is_same_v<T, long double>)
return std::stold(s, pos);
else
static_assert(always_false_v<T>,
"T is not a built-in floating point type");
}
The always_false_v variable template instead of just false is needed for old versions of the compilers that doesn't implement the new rules according to CWG2518.
Examples (the versions are inclusive):
false even in the current latest version.