我能否以constexpr的方式获取C++类型名称?

44

我希望在编译时使用类型名称。例如,假设我已经写了以下代码:

constexpr size_t my_strlen(const char* s)
{
        const char* cp = s;
        while(*cp != '\0') { cp++; };
        return cp - s;
}

现在我希望拥有:

template <typename T>
constexpr auto type_name_length = my_strlen(typeid(T).name());

然而,遗憾的是,typeid(T).name() 只是 const char*,不是 constexpr...是否有其他constexpr的方法可以获取类型名称?


你打算在编译时使用 type_name_length<T> 做什么呢?编译器通常很擅长评估 strlen() 并在可能的情况下给出一个常量。 - Barry
1
@Barry:我只是想在这里提供一个最小完整可复现示例,所以我编写了一个合成用例。 - einpoklum
@einpoklum 这很好;但在问题中添加一条评论(这只是一个MCVE,我真的在尝试X)也很好。 - Yakk - Adam Nevraumont
2
@Yakk:我确实说了“例如”和“假设”…… - einpoklum
@einpoklum 我的意思是,至少简要说明真正的动机,并与 MCVE 一起给出。 - Yakk - Adam Nevraumont
2
@Yakk: 这段代码是用于CUDA设备端调试的,它会打印类型名称并需要对输出进行对齐,我想在一个printf语句中将所有内容都放进去,而不需要使用循环来计算长度。 - einpoklum
3个回答

114

好的,你可以这样做,但可能不是完全可移植的:

struct string_view
{
    char const* data;
    std::size_t size;
};

inline std::ostream& operator<<(std::ostream& o, string_view const& s)
{
    return o.write(s.data, s.size);
}

template<class T>
constexpr string_view get_name()
{
    char const* p = __PRETTY_FUNCTION__;
    while (*p++ != '=');
    for (; *p == ' '; ++p);
    char const* p2 = p;
    int count = 1;
    for (;;++p2)
    {
        switch (*p2)
        {
        case '[':
            ++count;
            break;
        case ']':
            --count;
            if (!count)
                return {p, std::size_t(p2 - p)};
        }
    }
    return {};
}

你可以将期望的type_name_length定义为:

template <typename T>
constexpr auto type_name_length = get_name<T>().size;

演示(适用于clang和g++)


3
在MSVC上可以使用__FUNCSIG__实现类似的功能。 - melak47
1
我发现了这个网站,并测试了__FUNCSIG__。它似乎会发出完全替换/绑定的类型,就好像函数被显式实例化一样,而不是GCC的混合输出。例如:void __cdecl foo<double>(const double &), 对我来说一眼看上去不太有用。(GCC在执行替换时仍然对依赖类型与自由类型或推导类型与计算类型之间进行了一些奇怪的区分。)我认为反射功能被高度低估和支持不足,特别是在概念方面远远超出了视野... - John P
@JohnP:你认为你能把它编辑到提议的解决方案中吗? - einpoklum
一般来说,我认为我需要仔细查看规格 - 显然编译器总能以某种方式处理,而少数存在歧义的情况则有特殊语法来解决,例如最棘手的解析。我想做正确的事情,而不是让错误的答案存在。顺便说一句,如果有__PRETTY_FUNCTION__之类的情况,那么应该有一个__PRETTY_THIS__或类似的情况,对吗?在我看来,我们正在做“错误”的事情。 - John P
1
@melak47:看看我基于你的gist所写的答案。我放弃了使用异常,因为当你可以使用static_assert时,我不喜欢有那些东西... - einpoklum
显示剩余4条评论

15

编辑: 根据这个答案更新,它是由@HowardHinnant、@康桓瑋、@Val和我等多人精细改进的结果。

据我所知,语言标准没有提供任何获取类型名称的工具。因此,我们采用编译器特定的方法。这适用于GCC、clang和MSVC。

#include <string_view>
// If you can't use C++17's standard library, you'll need to use the GSL 
// string_view or implement your own struct (which would not be very difficult,
// since we only need a few methods here)

template <typename T> constexpr std::string_view type_name();

template <>
constexpr std::string_view type_name<void>()
{ return "void"; }

namespace detail {

using type_name_prober = void;

template <typename T>
constexpr std::string_view wrapped_type_name() 
{
#ifdef __clang__
    return __PRETTY_FUNCTION__;
#elif defined(__GNUC__)
    return __PRETTY_FUNCTION__;
#elif defined(_MSC_VER)
    return __FUNCSIG__;
#else
#error "Unsupported compiler"
#endif
}

constexpr std::size_t wrapped_type_name_prefix_length() { 
    return wrapped_type_name<type_name_prober>().find(type_name<type_name_prober>()); 
}

constexpr std::size_t wrapped_type_name_suffix_length() { 
    return wrapped_type_name<type_name_prober>().length() 
        - wrapped_type_name_prefix_length() 
        - type_name<type_name_prober>().length();
}

} // namespace detail

template <typename T>
constexpr std::string_view type_name() {
    constexpr auto wrapped_name = detail::wrapped_type_name<T>();
    constexpr auto prefix_length = detail::wrapped_type_name_prefix_length();
    constexpr auto suffix_length = detail::wrapped_type_name_suffix_length();
    constexpr auto type_name_length = wrapped_name.length() - prefix_length - suffix_length;
    return wrapped_name.substr(prefix_length, type_name_length);
}

也许使用“operator ()”或“operator const char*()”可以改善这个答案。 - vrqq
@vrqq:你能详细说明一下吗? - einpoklum
是的,喜欢我的答案。并且有一个潜在的错误,例如使用clang++时'typename'可能会出现两次,而在msvc中则是像'class mytype'这样的字符串。 - vrqq
1
还有需要注意的是,在使用 MSVC 编译器时(只有这个编译器?),它会在本地定义的类型前加上函数名,比如 enum func::Local (当然,这对诊断非常有帮助)。 - Sz.

2

这是一个可用于模板的替代答案,现在它可以使用g++、clang++和msvc运行。

修改自上面@einpoklum的答案:https://dev59.com/aFsV5IYBdhLWcg3wrAZJ#56600402

#include <iostream>
#include <string_view>

template<typename T>
struct TypeName {
    constexpr static std::string_view fullname_intern() {
        #if defined(__clang__) || defined(__GNUC__)
            return __PRETTY_FUNCTION__;
        #elif defined(_MSC_VER)
            return __FUNCSIG__;
        #else
            #error "Unsupported compiler"
        #endif
    }
    constexpr static std::string_view name() {
        size_t prefix_len = TypeName<void>::fullname_intern().find("void");
        size_t multiple   = TypeName<void>::fullname_intern().size() - TypeName<int>::fullname_intern().size();
        size_t dummy_len  = TypeName<void>::fullname_intern().size() - 4*multiple;
        size_t target_len = (fullname_intern().size() - dummy_len)/multiple;
        std::string_view rv = fullname_intern().substr(prefix_len, target_len);
        if (rv.rfind(' ') == rv.npos)
            return rv;
        return rv.substr(rv.rfind(' ')+1);
    }

    using type = T;
    constexpr static std::string_view value = name();
};

namespace s1 {
    class MyClass;
}

//Both MSVC, G++ and Clang++ have passed test.
int main () {
    static_assert(TypeName<s1::MyClass>::value == "s1::MyClass");
    std::cout<<"FULLNAME> "<<TypeName<void>::fullname_intern()<<std::endl;
    std::cout<<"TYPETEST> '"<<TypeName<s1::MyClass>::value<<"' == 's1::MyClass'"<<std::endl;
    return 0;
}

请注意:

在Clang++中的全名: static std::string_view TypeName<void>::fullname_intern() [T = void]

在G++中的全名: static constexpr std::string_view TypeName<T>::fullname_intern() [with T = void; std::string_view = std::basic_string_view<char>]

在MSVC中的全名: class std::basic_string_view<char,struct std::char_traits<char> > __cdecl TypeName<void>::fullname_intern(void)(但这里不是'class s1::MyClass'而是's1::MyClass')


为什么需要 4* multiple?为什么不只是 "void"sv.size() - Hedede

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接