tcb
tcb

Reputation: 4604

Extending std::to_string to support enums and pointers

I have the following template function ToString that uses std::to_string for arithmetic types and tries doing a static_cast for pointer and enum types.

#include <iostream>
#include <string>

template <typename T>
std::string ToString(T val)
{
    if (std::is_arithmetic<T>::value)
    {
        return std::to_string(val);
    }

    if (std::is_enum<T>::value || std::is_pointer<T>::value)
    {
        return std::to_string(static_cast<size_t>(val));
    }
}

enum class MyEnum : int
{
    E1,
    E2
};

int main(int argc, char* argv[])
{
    MyEnum e = MyEnum::E1;
    const void* ptr = &e;

    std::cout << ToString(e) << std::endl;
    std::cout << ToString(ptr) << std::endl;
}

The code above doesn't compile. Is there a way I can achieve the desired functionality?

The compilation errors on VS2017 are

Error   C2440   'static_cast': cannot convert from 'T' to 'size_t'
Error   C2665   'std::to_string': none of the 9 overloads could convert all the argument types

Upvotes: 1

Views: 822

Answers (3)

wreckgar23
wreckgar23

Reputation: 1045

This works with c++11. I hacked it together so I thought I may as well post it. You should pay attention to the other answers here though.

#include <cstdint>                                                                                                                                                                                                   
#include <string>                                                                                                                                                                                                    
#include <type_traits>                                                                                                                                                                                               
#include <iostream>                                                                                                                                                                                                  

template <typename T>                                                                                                                                                                                                
auto ToString(T val) -> typename std::enable_if<std::is_arithmetic<T>::value, std::string>::type                                                                                                                     
{                                                                                                                                                                                                                    
        return std::to_string(val);                                                                                                                                                                                  
}                                                                                                                                                                                                                    

template <typename T>                                                                                                                                                                                                
auto ToString(T val) -> typename std::enable_if<std::is_enum<T>::value, std::string>::type                                                                                                                           
{                                                                                                                                                                                                                    
        return std::to_string(static_cast<typename std::underlying_type<T>::type>(val));                                                                                                                                                             
}                                                                                                                                                                                                                    


template <typename T>                                                                                                                                                                                                
auto ToString(T val) -> typename std::enable_if<std::is_pointer<T>::value, std::string>::type                                                                                                                        
{                                                                                                                                                                                                                    
        return std::to_string(reinterpret_cast<std::uintptr_t>(val));                                                                                                                                                        
}                                                                                                                                                                                                                    

enum class MyEnum : int                                                                                                                                                                                              
{                                                                                                                                                                                                                    
        E1,                                                                                                                                                                                                          
        E2                                                                                                                                                                                                           
};                                                                                                                                                                                                                   

int main(int argc, char* argv[])                                                                                                                                                                                     
{                                                                                                                                                                                                                    
        MyEnum e = MyEnum::E1;                                                                                                                                                                                       
        const void* ptr = &e;                                                                                                                                                                                        

        std::cout << ToString(e) << std::endl;                                                                                                                                                                       
        std::cout << ToString(ptr) << std::endl;                                                                                                                                                                     
}

Upvotes: 1

eerorika
eerorika

Reputation: 238341

You need to use if constexpr. Otherwise you instantiate std::to_string with a pointer or enum, which fails to compile (C2665).

Futhermore you cannot static cast pointer to an integer (C2440). You need reinterpret cast.

Also, your function lacks a return if the passed argument is neither enum, pointer nor an arithmetic type. In such case the behaviour is undefined. Solution: Always return something (or fail compilation if template arguments are invalid).

Also, size_t is not guaranteed to be large enough to represent all pointer values. you want std::uintptr_t.

And you'll probably want to use std::underlying_type_t to get the correct type for enum class.

if constexpr (std::is_pointer<T>::value) {
    return std::to_string(reinterpret_cast<std::uintptr_t>(val));
} else if constexpr (std::is_enum<T>::value)
{
    return std::to_string(static_cast<std::underlying_type_t<T>>(val));
} else {
    return std::to_string(val);
}

Once you add the missing headers, this should work.

P.S. Design note: Now printing a pointer, integer or enum are all identical in output. You might want to add some prefix or similar to disambiguate the result.

Upvotes: 4

templatetypedef
templatetypedef

Reputation: 372784

I think the immediate issue here is the static_cast if T is a pointer type. To convert a pointer to an integer, you need to use a reinterpret_cast rather than a static_cast, since that cast fundamentally reinterprets the bits of the pointer variable as a number rather than simply telling C++ to change what kind of object if thinks is being pointed at.

As the comments have pointed out, you’ll likely need to do some restructuring on your code to get it to work correctly, because when you instantiate the template, all the code in the body of the function will be compiled with that choice of T. To add to the list of suggestions of how to do this, if you’re using a C++17 compiler, consider using if constexpr in your code instead of a regular if. This will tell C++ to only conditionally compile the different branches of the if/else chain.

Upvotes: 1

Related Questions