可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Right now I use the following piece of code to dummily convert basic types (int
, long
, char[]
, this kind of stuff) to std::string
for further processing:
template<class T>
constexpr std::string stringify(const T& t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
however I don't like the fact that it depends on std::stringstream
. I tried using std::to_string
(from C++11's repertoire) however it chokes on char[]
variables.
Is there a simple way offering an elegant solution for this problem?
回答1:
As far as I know the only way of doing this is by specialising the template by the parameter type with SFINAE.
You need to include the type_traits.
So instead of your code use something like this:
template<class T>
typename std::enable_if<std::is_fundamental<T>::value, std::string>::type stringify(const T& t)
{
return std::to_string(t);
}
template<class T>
typename std::enable_if<!std::is_fundamental<T>::value, std::string>::type stringify(const T& t)
{
return std::string(t);
}
this test works for me:
int main()
{
std::cout << stringify(3.0f);
std::cout << stringify("Asdf");
}
Important note: the char arrays passed to this function need to be null terminated!
As noted in the comments by yakk you can get rid of the null termination with:
template<size_t N> std::string stringify( char(const& s)[N] ) {
if (N && !s[N-1]) return {s, s+N-1};
else return {s, s+N};
}
回答2:
Is there a simple way offering an elegant solution for this problem?
Since nobody proposed it, consider using boost::lexical_cast.
This integrates seamlessly with anything that implements std::ostream<< operator and can be extended for custom types.
回答3:
I'd recommend using enable_if_t
and if you're going to take in any single character variables you specialize those:
template<typename T>
enable_if_t<is_arithmetic<T>::value, string> stringify(T t){
return to_string(t);
}
template<typename T>
enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<>
string stringify<char>(char t){
return string(1, t);
}
Here I'm just specializing char
. If you need to specialize wchar
, char16
, or char32
you'll need to do that as well.
Anyway for non-arithmetic types these overloads will default to using ostringstream
which is good cause if you've overloaded the extraction operator for one of your classes this will handle it.
For arithmetic types this will use to_string
, with the exception of char
and anything else you overload, and those can directly create a string
.
Edit:
Dyp suggested using whether to_string
accepts an argument of T::type
as my enable_if_t
condition.
The simplest solution is only available to you if you have access to is_detected
in #include <experimental/type_traits>
. If you do just define:
template<typename T>
using to_string_t = decltype(to_string(declval<T>()));
Then you can set your code up as:
template<typename T>
decltype(to_string(T{})) stringify(T t){
return to_string(t);
}
template<typename T>
enable_if_t<!experimental::is_detected<to_string_t, T>::value, string> (T t){
return static_cast<ostringstream&>(ostringstream() << t).str();
}
template<>
string stringify<char>(char t){
return string(1, t);
}
I asked this question to figure out how to use to_string
as my condition. If you don't have access to is_detected
I'd highly recommend reading through some of the answers cause they are phenomenal: Metaprograming: Failure of Function Definition Defines a Separate Function
回答4:
The simplest solution is to overload for the types you want:
using std::to_string;
template<size_t Size>
std::string to_string(const char (&arr)[Size])
{
return std::string(arr, Size - 1);
}
since to_string
isn't a template you can't specialize it, but fortunately this is easier.
The code assumes the array is null terminated, but is still safe if it is not.
You may also want to put the using
line inside the functions that call to_string
if you have strong feelings about where using
belongs.
This also has the benefit that if you pass it a non-null-terminated string somehow, it does not have UB as the one argument std::string
constructor does.
回答5:
Although the the question is not of a gimme the code kind, since I already have a solution implemented I thought of sharing it:
template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
-> std::string;
template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string;
template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string;
template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string;
inline auto buildString() -> std::string { return {}; }
template <class... Tail>
inline auto buildString(std::string const &head, Tail const &... tail)
-> std::string {
return head + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char const *head, Tail const &... tail) -> std::string {
return std::string{head} + buildString(tail...);
}
template <class... Tail>
inline auto buildString(char *head, Tail const &... tail) -> std::string {
return std::string{head} + buildString(tail...);
}
template <class Head, class... Tail>
inline auto buildString(Head const &head, Tail const &... tail) -> std::string {
return std::to_string(head) + buildString(tail...);
}
Usage:
auto gimmeTheString(std::string const &str) -> void {
cout << str << endl;
}
int main() {
std::string cpp_string{"This c++ string"};
char const c_string[] = "this c string";
gimmeTheString(buildString("I have some strings: ", cpp_string, " and ",
c_string, " and some number ", 24));
return 0;
}
回答6:
I believe, the most elegant solution is:
#include <string>
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
template <typename T>
typename std::enable_if<!std::is_constructible<std::string, T>::value, std::string>::type
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
Here, if we can construct std::string
using T
(we check it with help of std::is_constructible<std::string, T>
), then we do it, otherwise we use to_string
.
Of course, in C++14 you can replace typename std::enable_if<...>::type
with much shorter std::enable_if_t<...>
. An example is in the shorter version of the code, right below.
The following is a shorter version, but it's a bit less efficient, because it needs one extra move of std::string
(but if we do just a copy instead, it's even less efficient):
#include <string>
std::string stringify(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
template <typename T>
std::enable_if_t<!std::is_convertible<T, std::string>::value, std::string>
stringify(T&& value) {
using std::to_string; // take advantage of ADL (argument-dependent lookup)
return to_string(std::forward<T>(value)); // take advantage of perfect forwarding
}
This version uses implicit conversion to std::string
then possible, and uses to_string
otherwise. Notice the usage of std::move
to take advantage of C++11 move semantics.
Here is why my solution is better than the currently most voted solution by @cerkiewny:
It have much wider applicability, because, thanks to ADL, it is also
defined for any type for which conversion using function to_string
is defined (not only std::
version of it), see the example usage below.
Whereas the solution by @cerkiewny only works for the fundamental
types and for the types from which std::string is constructible.
Of course, in his case it is possible to add extra overloads of
stringify
for other types, but it is a much less solid solution if
compared to adding new ADL versions of to_string
. And chances are
height, that ADL-compatible to_string
is already defined in a third party library for a
type we want to use. In this case, with my code you don't need to write any additional code at all to make stringify
work.
It is more efficient,
because it takes advantage of C++11 perfect forwarding (by using universal references (T&&
) and std::forward
).
Example usage:
#include <string>
namespace Geom {
class Point {
public:
Point(int x, int y) : x(x), y(y) {}
// This function is ADL-compatible and not only 'stringify' can benefit from it.
friend std::string to_string(const Point& p) {
return '(' + std::to_string(p.x) + ", " + std::to_string(p.y) + ')';
}
private:
int x;
int y;
};
}
#include <iostream>
#include "stringify.h" // inclusion of the code located at the top of this answer
int main() {
double d = 1.2;
std::cout << stringify(d) << std::endl; // outputs "1.200000"
char s[] = "Hello, World!";
std::cout << stringify(s) << std::endl; // outputs "Hello, World!"
Geom::Point p(1, 2);
std::cout << stringify(p) << std::endl; // outputs "(1, 2)"
}
Alternative, but not recommended approach
I also considered just overloading to_string
:
template <typename T>
typename std::enable_if<std::is_constructible<std::string, T>::value, std::string>::type
to_string(T&& value) {
return std::string(std::forward<T>(value)); // take advantage of perfect forwarding
}
And a shorter version using implicit conversion to std::string
:
std::string to_string(std::string s) { // use implicit conversion to std::string
return std::move(s); // take advantage of move semantics
}
But these have serious limitations: we need to remember to write to_string
instead of std::to_string
everywhere where we want to use it; also it is incompatible with the most common ADL usage pattern:
int main() {
std::string a = std::to_string("Hello World!"); // error
using std::to_string; // ADL
std::string b = to_string("Hello World!"); // error
}
And it's most probable, there are other problems connected with this approach.