C ++将简单值转换为字符串

时间:2015-05-11 11:39:29

标签: c++ c++11 stringstream string-conversion

现在我使用以下代码将基本类型(intlongchar[],此类内容)dummily转换为std::string以进行进一步处理:

template<class T>
constexpr std::string stringify(const T& t)
{
    std::stringstream ss;
    ss << t;
    return ss.str();
}
但是,我不喜欢它取决于std::stringstream的事实。我尝试使用std::to_string(来自C ++ 11&#39; s保留曲目),但它会在char[]个变量上窒息。

有没有一种简单的方法可以为这个问题提供优雅的解决方案?

5 个答案:

答案 0 :(得分:10)

据我所知,这样做的唯一方法是通过SFINAE的参数类型对模板进行专门化。

您需要包含type_traits。

所以代替你的代码使用这样的东西:

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);
  }

这个测试对我有用:

int main()
{
  std::cout << stringify(3.0f);
  std::cout << stringify("Asdf");
}

重要提示:传递给此函数的char数组需要以null结尾!

如yakk的评论中所述,您可以通过以下方式摆脱空终止:

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}; 
}

答案 1 :(得分:9)

  

有没有一种简单的方法可以为这个问题提供优雅的解决方案?

由于没有人提出,请考虑使用boost::lexical_cast

这与实现std :: ostream&lt;&lt;的任何内容无缝集成。运算符,可以扩展为自定义类型。

答案 2 :(得分:5)

我建议使用enable_if_t,如果您要接受任何单个字符变量,那么您需要专注于这些变量:

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);
}

这里我只是专攻char。如果您需要专门化wcharchar16char32,那么您也需要这样做。

无论如何,对于非算术类型,这些重载将默认使用ostringstream,这是一个很好的原因,如果你已经为你的一个类重载了它将处理它的提取操作符。

对于算术类型,这将使​​用to_string,但char和您重载的任何其他内容除外,这些可以直接创建string

修改

Dyp suggested使用to_string是否接受T::type的参数作为enable_if_t条件。

只有is_detected访问#include <experimental/type_traits>时才能使用最简单的解决方案。如果你只是定义:

template<typename T>
using to_string_t = decltype(to_string(declval<T>()));

然后您可以将代码设置为:

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);
}

我问this question弄清楚如何使用to_string作为我的条件。如果您无法访问is_detected,我强烈建议您阅读一些答案,因为它们非常现象:Metaprograming: Failure of Function Definition Defines a Separate Function

答案 3 :(得分:2)

虽然问题不在于 gimme the code 类,但由于我已经实现了解决方案,我想分享它:

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...);
}

用法:

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;
}

答案 4 :(得分:2)

我相信,最优雅的解决方案是:

#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
}

在这里,如果我们可以使用std::string构建T(我们在std::is_constructible<std::string, T>的帮助下进行检查),那么我们会这样做,否则我们会使用to_string

当然,在C ++ 14中,您可以将typename std::enable_if<...>::type替换为更短的std::enable_if_t<...>。一个例子是在较短版本的代码中,就在下面。

以下是较短的版本,但效率稍低,因为它需要额外移动std::string(但如果我们只做一个副本,那么效率会更低):

#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
}

此版本使用隐式转换为std::string,然后使用to_string。请注意使用std::move来利用C ++ 11 move semantics

这就是为什么我的解决方案优于@cerkiewny目前投票最多的solution

  • 它有更广泛的适用性,因为,感谢ADL,它也是 为使用函数to_string进行转换的任何类型定义 已定义(不仅是std::版本),请参阅下面的示例用法。 而@cerkiewny的解决方案仅适用于基础 类型和std :: string可构造的类型。

    当然,在他的情况下,可以添加额外的重载 stringify适用于其他类型,但如果是这样的解决方案则不太可行 与添加to_string的新ADL版本相比。而且机会是 高度,ADL兼容to_string已在第三方库中定义 我们想要使用的类型。在这种情况下,使用我的代码,您无需编写任何其他代码即可使stringify正常工作。

  • 效率更高, 因为它利用了C ++ 11 perfect forwarding(通过使用通用引用(T&&)和std::forward)。

使用示例:

#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)"
}

替代方案,但不推荐方法

我还考虑过度重载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
}

使用隐式转换为std::string的较短版本:

std::string to_string(std::string s) { // use implicit conversion to std::string
    return std::move(s); // take advantage of move semantics
}

但这些都有严重的局限性:我们需要记住在我们想要使用它的地方写to_string而不是std::to_string;它也与最常见的ADL使用模式不兼容:

int main() {
    std::string a = std::to_string("Hello World!"); // error

    using std::to_string; // ADL
    std::string b = to_string("Hello World!"); // error
}

最可能的是,这种方法还存在其他问题。