【问题标题】:Converting std::type_identity object to a type将 std::type_identity 对象转换为类型
【发布时间】:2021-01-17 05:02:23
【问题描述】:

假设我们创建两个返回std::type_identityliketype_of函数:

template<auto VAR>
auto type_of() {
    return std::type_identity<decltype(VAR)>{};
}

template<typename T>
auto type_of() {
    return std::type_identity<T>{};
}

std::type_identity获取实际类型的方式似乎有点麻烦:

// this works
// both i1 and i2 are ints
decltype(type_of<int>())::type i1;
decltype(type_of<int{}>())::type i2;

有没有办法在上面的表达式中放弃对decltype 的需要,或者将它放在一个可重用的表达式中,以实现更好的效果,例如:

// can this work?
type_of<int>()::type i1;
type_of<int{}>()::type i2;

甚至更好:

// can this work??
type_of_t<int> i1;
type_of_t<int{}> i2;

注意:类型和非类型模板参数的特化,可能是一个方向,不起作用(无法编译):

template<auto>
struct type_of;

template<typename T>
struct type_of<T> { // <== compilation error: type/value mismatch 
    using type = T;
};

template<auto VAR>
struct type_of<VAR> {
    using type = decltype(VAR);
};

【问题讨论】:

  • 你想达到什么目的?如果您知道T 是一个类型,只需使用T i1 来创建该类型的变量。如果你知道t是一个变量,只需使用decltype(t) i2创建一个相同类型的变量。
  • type_of 函数可以返回任何类型。 type_of 的实现只是最简单的例子。
  • 像你一样使用 type_identity 是 0 点。只需为您尝试传输的类型使用类型别名。当您真正想要的是传递 int 类型时,为什么要传递 int 的 type_identity?对我来说毫无意义。换句话说:不要使用不需要的东西。许多 C++ 库是“按需提供”的,只有在您明确需要时才应使用。在这里,我根本看不到任何需要。这就像有人阅读了设计模式并试图为所有事情选择一种,结果却是企业的混乱结果。
  • 如果你想返回一个类型,你为什么要使用根据定义返回对象的函数?在 C++ 中,类型是从相当于元函数的东西返回的,并且如您所见,存在阻抗不匹配,而 type_identity 和类似的解决方案并不能解决这个问题。考虑到语言的现状,这不是一个可以解决的问题——直到类型成为一等值。一旦语言允许将类型作为值返回,然后在需要类型的地方使用这些值,问题就会得到解决。到目前为止,只有元函数的使用负担会更小,因为它们本身就是类型。
  • 你在寻找类似this的东西吗?

标签: c++ c++20 typetraits


【解决方案1】:

您可以创建类型别名。但是,您不能“重载”它。所以我的解决方案是创建两个:

template <auto Var>
using type_of_var_t = decltype(type_of<Var>())::type;

template <class T>
using type_of_t = decltype(type_of<T>())::type;

auto test()
{
    type_of_var_t<11> i1 = 24;
    type_of_t<int> i2 = 17;

}

【讨论】:

  • 当然,但我们的想法是让调用方不知道模板参数是类型还是变量,主要是为了易于使用。
【解决方案2】:

在 C++ 中,模板参数必须是一个值、一个类型或另一个模板(它本身必须适合声明的模板头)。它一定是其中之一。

如果你想这样做:

这个想法是让调用方不知道模板参数是类型还是变量

能够做到这一点的基本要求是编写一个模板,其参数可以是一个值一个类型。

C++ 不允许这样做。

模板函数重载可以让您摆脱之类的。但这仅有效,因为它不是一个模板。重载的是 两个 模板。选择哪一个取决于提供的模板参数。

模板类不能重载。并且模板专业化不能改变原始模板的性质(比如它的模板参数是什么)。它只能允许您重新解释原始模板参数的模板参数,以便提供替代实现。

如果你想要这个,你将不得不等到 C++ 能够拥有可以是任何东西的模板参数,或者直到 C++ 能够将类型转换为值并返回(即:反射)。

【讨论】:

    【解决方案3】:

    std::type_identity对象can be encapsulated into the following expresion获取类型:

    template<auto x>
    using type = typename decltype(x)::type;
    

    这将允许替换繁琐的表达式:

    decltype(type_of<int>())::type i1;
    decltype(type_of<int{}>())::type i2;
    

    用更简单的表达方式:

    type<type_of<int>()> i1;
    type<type_of<int{}>()> i2;
    

    它仍然需要经过两个步骤type_of 然后type)因为第一个应该能够得到一个类型 em>或变量,只适用于函数模板重载,则函数不能返回类型,所以返回一个需要模板表达式提取内部类型的对象。


    根据您要对类型执行的操作,代码可以变得更加简单。

    如果您只想创建该类型的对象,您可以将对象的创建转发到函数中:

    template<auto VAR, typename... Args>
    auto create_type_of(Args&&... args) {
        return decltype(VAR){std::forward<Args>(args)...};
    }
    
    template<typename T, typename... Args>
    auto create_type_of(Args&&... args) {
        return T{std::forward<Args>(args)...};
    }
    
    auto i1 = create_type_of<int>(7);
    auto i2 = create_type_of<int{}>(7);
    

    以这种方式从std::type_identitycan work创建类型的一般情况:

    template<auto VAR>
    constexpr auto type_of() {
        return std::type_identity<decltype(VAR)>{};
    }
    
    template<typename T>
    constexpr auto type_of() {
        return std::type_identity<T>{};
    }
    
    template<typename T, typename... Args>
    auto create(std::type_identity<T>, Args&&... args) {
        return T{std::forward<Args>(args)...};
    }
    
    auto i1 = create(type_of<int>(), 7);
    auto i2 = create(type_of<int{}>(), 7);
    

    请注意,整个事情只适用于可用作非类型模板参数的变量。对于更通用的方法,无需模板(但使用宏......),因此可以用于不能作为模板参数的变量,请参阅this mind blowing neat answer!

    【讨论】:

    • 这如何解决问题?您写的问题是关于获取值的类型,而不是基于该类型创建对象。
    • @NicolBolas 更新了答案,以更优雅的方式处理获取类型
    【解决方案4】:

    按照设计,C++ 模板中的模板参数是模板、类型或值。

    在模板中,您知道它们是什么。模板中依赖于模板参数的所有表达式都使用 typenametemplate 关键字(或上下文)消除歧义,因此在替换参数之前它们的类别是已知的。

    现在有元编程库可以使用所有 3 的值替代。

    template<class T> struct type_tag_t {using type=T;};
    template<class T> constexpr type_tag_t<T> tag={};
    template<template<class...>class Z> struct template_z_t {
      template<class...Ts>
      using result = Z<Ts...>;
      template<class...Ts>
      constexpr result<Ts...> operator()( type_tag_t<Ts>... ) const { return {}; }
    };
    template<template<class...>class Z>
    constexpr template_z_t<Z> template_z = {};
    

    这里的模板被映射到 constexpr 函数对象。 template_z 模板允许您将类型模板映射到此域。

    template<auto x>
    using type = typename decltype(x)::type;
    
    template<auto x>
    constexpr std::integral_constant<std::decay_t<decltype(x)>, x> k = {};
    

    所以,

    constexpr auto vector_z = template_z<std::vector>;
    constexpr auto vector_tag = vector_z( tag<int>, tag<std::allocator<int>> );
    

    然后你可以回到类型:

    type<vector_tag> v{1,2,3,4};
    

    这可能不是你想要的。

    【讨论】:

      【解决方案5】:

      您可能愿意查看Universal Template Parameters 的提案

      暗示示例与您专门针对 TTP 和 NTTP 的用例非常匹配:

      template <template auto>
      struct X;
      template <typename T>
      struct X<T> {
         // T is a type
         using type = T;
      };
      template <auto val>
      struct X<val> : std::integral_constant<decltype(val), val> {
         // val is an NTTP
      };
      

      【讨论】:

        猜你喜欢
        • 2018-11-27
        • 2012-12-11
        • 2013-05-23
        • 1970-01-01
        • 2011-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多