首页 > 编程语言 >C++ 20 编译期类型名获取

C++ 20 编译期类型名获取

时间:2023-11-20 22:02:57浏览次数:40  
标签:std __ 20 name constexpr auto C++ 编译 end

编译期类型名获取

C++20 标准,使用库 std::source_location

#include <source_location>

C++ 20 之前

在 C++ 20 前有两种方法

  • __PRETTY_FUNCTION__ __FUNCSIG__

通过截取函数签名中的 T = ... 获取函数类型。

template <typename T>
constexpr auto type_name() -> std::string_view
{
    using std::literals::string_view_literals::operator""sv;
    constexpr auto prefix = "T = "sv;
    constexpr auto suffix = ";]"sv;
    constexpr std::string_view detail = __PRETTY_FUNCTION__;

    constexpr auto pre_rng = std::ranges::search(detail, prefix);
    static_assert(!pre_rng.empty());

    constexpr std::ranges::subrange subrange {pre_rng.end(), detail.end()};
    constexpr auto suf = std::ranges::find_first_of(subrange, suffix);
    static_assert(suf != detail.end());

    return {pre_rng.end(), std::distance(pre_rng.end(), suf)};
}

缺点:两者都不是标准宏,且 __PRETTY_FUNCTION__ 为 GCC 拓展。而且宏也越来越被标准库中的函数代替,比如本文就是使用 std::source_location 库中的 function_name() 代替 __PRETTY_FUNCTION__

还有两个个作用相似的宏 __FUNCTION____func__(C99 标准),不过它们只能获取函数名,没有模版中的类型。实际上这些宏都是由编译器隐式定义在每个函数中的(只读)变量。

  • std::type_info abi::__cxa_demangle
template <typename T>
auto type_name() -> std::string
{
    std::type_info const& t = typeid(T);
    char const* name = t.name();

    int status;
    std::unique_ptr<char, void (*)(void*)> res {
        abi::__cxa_demangle(name, NULL, NULL, &status),
        std::free
    };
    return status == 0 ? res.get() : name;
}

其中 typeid(T).name() 返回的是运行时获取的类型签名, abi::__cxa_demangle 是 GCC 提供的将类型签名转换为类型名的函数(非 GCC 就没有这个函数了)。

typeid(T).name() 获得的类型签名不一定等于类型名,比如 typeid(std::string).name() 得到的签名就可能是 NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE,所以需要 abi::__cxa_demangle 进行转换。

缺点:非编译期,且 cppreference 提到 std::type_info::name() 并不提供保证类型签名是唯一的,因此具有一定的不确定性。

std::type_info

std::type_info::name)Returns an implementation defined null-terminated character string containing the name of the type. No guarantees are given; in particular, the returned string can be identical for several types and change between invocations of the same program.

std::source_location

使用 std::source_location::current().function_name() 可以获取这个语句所在的函数的函数签名。实际上是取代了 __PRETTY_FUNCTION__。可以在 编译器支持 表中查看对不同编译器对 source_location 的支持,现在主流的编译器(GCC11、Clang16)都已经支持了。

std::source_location::function_name()constexpr 函数,因此可以在编译期就获取到函数名称。

更多信息请查看 cppreference,其中还有函数 line() column() file_name() 等可以代替 __LINE__ __FILE__ 等宏。

编译期顺序

特别要注意编译期的顺序

比较下面两个函数:

template <typename T, typename U>
consteval auto foo() -> std::string_view
{
    constexpr auto p = std::source_location::current().function_name();
    return p;
}

template <typename T, typename U>
consteval auto fun() -> std::string_view
{
    return std::source_location::current().function_name();
}

如果编译器在每次编译期将 foo() 完全重新计算一遍 p,那么 foo()fun() 的结果是相同的;如果编译器只将 p 计算一次,此时每次调用 foo() 得到的结果都是第一次的 p

不同编译器对在编译期计算顺序是不同的。

实战:类型实例表

  • get<T>() 从表中获取 T 的实例;
  • put<T>(Args...) 在表中放置 T 的实例。

任何想要转换为 std::any 的类型要具有拷贝构造函数 T(T const&),为了避免这一点,填入表中的实例是类型 std::shared_ptr<T> 而不是 T,因为 std::shared_ptr 总是可以拷贝的。

#include <source_location>
#include <any>
#include <map>
#include <memory>
#include <string_view>
  1. 禁止 const 类型和 reference 类型,也就是说 get<T&> get<T const> 之类的无法通过编译,我们需要的是纯粹的类型。使用 concept 设置这个限制。
template <typename T>
concept is_not_cr = !std::is_const_v<T> && !std::is_reference_v<T>;

template 中用 is_not_cr 声明类型即可应用这一限制,比如使用 name_detail() 转发 function_name() 函数的实现。再添加 consteval 强制在编译期求值。

template <is_not_cr T>
static consteval auto name_detail() noexcept -> std::string_view
{
    return std::source_location::current().function_name();
}

GCC 的实现会用 <function> [with <template>; ...],且类型信息更丰富,会带有函数限制符号 constexpr 等。Clang 的实现会用 <function> [<template>; ...]。其中 <template>T = ...,即模版中代表类型的名称。总之在不同的编译器实现中都要定位 T = ... 这一句。

使用 std::ranges 中的函数(都是 constexpr 函数),它们都需要接受 range 类的参数,也就是说 char const* 不行(只有开头,没有结尾、长度),需要 std::string_view 类型。

template <is_not_cr T>
static consteval auto type_name() noexcept -> std::string_view
{
    using std::literals::string_view_literals::operator""sv;
    constexpr auto prefix = "T = "sv;
    constexpr auto suffix = ";]"sv;
    constexpr auto detail = name_detail<T>();

    constexpr auto pre_rng = std::ranges::search(detail, prefix);
    static_assert(!pre_rng.empty());

    constexpr std::ranges::subrange subrange {pre_rng.end(), detail.end()};
    constexpr auto suf = std::ranges::find_first_of(subrange, suffix);
    static_assert(suf != detail.end());

    return {pre_rng.end(), std::distance(pre_rng.end(), suf)};
}

解决了类型名称获取的问题,现在就是使用 std::map 存放其与对应类型实例的映射了。

使用 std::any 来存储任意类型(其实是 C 语言中 void* 的代替),通过 std::any_cast 进行类型转换。

语义上,std::optional<T&> 应该更符合 get() 的返回值,但不存在这种类型。所以使用 std::shared_ptr<T> 来返回。或者你也可以选择在没有对应的键的情况下直接抛出错误。

  • Args&& 万能引用和 std::forward 转发,令参数在传入 put() 时和传给 new T() 时保持参数的引用类型和 const 限制。
  • put() 返回可能的本来存在于 map 中的实例。
template <is_not_cr T>
static auto get() -> ptr<T>
{
    if (auto find = map.find(type_name<T>()); find != map.end()) {
        return std::any_cast<ptr<T>>(find->second);
    }
    return {};
}

template <is_not_cr T, typename... Args>
static auto put(Args&&... args) -> ptr<T>
{
    ptr<T> p {new T(std::forward<Args>(args)...)};
    auto [it, b] = map.try_emplace(type_name<T>(), std::move(p));
    if (b) return {};
    p.swap(std::any_cast<decltype((p))>(it->second));
    return p;
}

全部代码如下

#include <any>
#include <map>
#include <memory>
#include <source_location>
#include <string_view>

template <typename T>
concept is_not_cr = !std::is_const_v<T> && !std::is_reference_v<T>;

class Instance
{
    Instance() = delete;

    template <is_not_cr T>
    using ptr = std::shared_ptr<T>;

    template <is_not_cr T>
    static consteval auto name_detail() noexcept -> std::string_view
    {
        return std::source_location::current().function_name();
    }

    static std::map<std::string_view, std::any> map;

public:
    template <is_not_cr T>
    static consteval auto type_name() noexcept -> std::string_view
    {
        using std::literals::string_view_literals::operator""sv;
        constexpr auto prefix = "T = "sv;
        constexpr auto suffix = ";]"sv;
        constexpr auto detail = name_detail<T>();

        constexpr auto pre_rng = std::ranges::search(detail, prefix);
        static_assert(!pre_rng.empty());

        constexpr std::ranges::subrange subrange {
            pre_rng.end(), detail.end()
        };
        constexpr auto suf = std::ranges::find_first_of(subrange, suffix);
        static_assert(suf != detail.end());

        return {pre_rng.end(), std::distance(pre_rng.end(), suf)};
    }

    template <is_not_cr T>
    static auto get() -> ptr<T>
    {
        if (auto find = map.find(type_name<T>()); find != map.end()) {
            return std::any_cast<ptr<T>>(find->second);
        }
        return {};
    }

    template <is_not_cr T, typename... Args>
    static ptr<T> put(Args&&... args)
    {
        ptr<T> p {new T(std::forward<Args>(args)...)};
        auto [it, b] = map.try_emplace(type_name<T>(), p);
        if (b) return {};
        p.swap(std::any_cast<decltype((p))>(it->second));
        return p;
    }
};

std::map<std::string_view, std::any> Instance::map;

标签:std,__,20,name,constexpr,auto,C++,编译,end
From: https://www.cnblogs.com/violeshnv/p/17844993.html

相关文章

  • ISSCC2024 Computing-In-Memory Session 趋势整理
    ISSCC2024Computing-In-MemorySession趋势整理今天上午ISSCC2024远东区推介会,主要关注了一下Computing-In-MemorySession。CIM今年被放在了Session34,会上主持人透露CIM方向一共投稿了50篇,最后录用了9篇,算下来录用率不到20%,不得不感慨一句相当之卷。言归正题,以下是今年CIMS......
  • NOIP2023
    T1:词典题意:给定\(n\)个长度为\(m\)的字符串\(w_1,w_2,\cdots,w_n\)。对于每个\(i=1,2,\cdots,n\)询问是否存在\(w_1',w_2',\cdots,w_n'\)使得对于每个\(j=1,2,\cdots,n\),\(w_j'\)都可以由\(w_j\)交换字符得到,且对于\(j\neqi\)都有\(w......
  • JetBrains TeamCity 任意代码执行漏洞(CVE-2023-42793)研究
    一、JetBrainsTeamCity简介TeamCity是一款由JetBrains开发的强大的持续集成(ContinuousIntegration,CI)和持续部署(ContinuousDeployment,CD)工具。它帮助开发团队自动化构建、测试和部署过程,以确保软件项目的质量和快速交付。TeamCity的主要特点和优势包括:灵活的构建配......
  • 【尝试逆向】零基础尝试寻找某个C++游戏的文件读取方法
    前言本游戏在国内知名度非常一般,而且在游戏领域也算是非常少见的厂商完全不考虑国际化的游戏系列,距今已有近30年的历史。这次为了尝试对此游戏的贴图进行提取,我尝试下载了本游戏系列的大概所有版本,并尝试通过脱壳等手段找到贴图的提取函数,并想办法写出来提取用的脚本。不过目前......
  • Spring_2023_11_20_2 -DI 依赖注入=》构造方式的形式
    DI依赖注入=》构造方式的形式构造方法的注入,使用实体类对象进行注入Student类集合的注入(数组、List、Set、Map)<!--<bean/>等同于newStudent()通过构造方法的形式进行依赖注入constructor-arg:构造方法参数的注入标签1.index:下表,构......
  • HUAWEI SECURITY 2023 山东大学专场 WP
    CryptobySmera1d01.ezrsa题干如下:fromCrypto.Util.numberimportgetPrimefromsecretimportflagp=getPrime(512)print(p,pow(flag,2,p))给出了\(p\)和\({flag}^2modp\)即我们需要解一个已知\(n\)和\(p\),求解\(x^2=n(modp)\)中\(x\)的值上网查阅发现\(Tonelli......
  • Linux训练营(gcc编译器)
    (文章目录)前言本篇文章我们来讲解gcc编译器,gcc编译器在Linux中是用来将从代码编译为可执行程序。为了更加深入了解程序编译生成的过程我们有必要来学习gcc的相关知识。一、gcc编译器介绍GCC(GNUCompilerCollection)是一款广泛使用的开源编译器套件,由GNU项目开发。它支持多种......
  • 11.20每日总结
    B/S结构用户界面设计    【实验编号】10003809548jWeb界面设计【实验学时】8学时【实验环境】l 所需硬件环境为微机;l 所需软件环境为dreamweaver【实验内容】这次实验要设计一个B/S结构的用户界面,题目自拟,我刚开始的选题是潮鞋售卖,所以我要做......
  • 20231120
    运行flash文件真是一件难事,不如直接转化为mp4通过本次的实验也是学习到了html界面中如何运行swf文件,也是了解到了flash的流氓性。更加深刻的了解到了人机交互技术的重要性。     ......
  • 【C++】【OpenCV】【NumPy】图像数据的访问
    接上一随笔,这次学习针对图像数据的访问(Numpy.array)在OpenCV中,使用imread()方法可以访问图像,其返回值是一个数组,而根据传入的不同图像,将会返回不同维度的数组。针对返回的图像数据,即数组,我们是可以进行操作的:1importcv223#MyPic.png图像自行随意创建一个原始字符转换......