【问题标题】:How do I test if a type is within another class exists?如何测试一个类型是否存在于另一个类中?
【发布时间】:2021-12-23 07:30:57
【问题描述】:

我在想如果一个类包含一个类型,我可以测试(使用 C++14),我可以这样做:

#include <type_traits>

struct X {
  using some_type = int;
};
struct Y {};

template <typename T, typename = void>
struct has_some_type : std::false_type {};

template <typename C>
struct has_some_type<C, typename C::some_type> : std::true_type {};

static_assert(has_some_type<X>::value); // unexpectedly fails
static_assert(has_some_type<Y>::value); // correctly fails

但是static_assert 失败了,这让我很吃惊,因为检查成员函数会以类似的方式工作。

#include <type_traits>

struct X {
  void some_function();
};
struct Y {};

template <typename T, typename = void>
struct has_some_function : std::false_type {};

template <typename C>
struct has_some_function<C, decltype(std::declval<C>().some_function())> : std::true_type {};

static_assert(has_some_function<X>::value); // correctly succeeds
static_assert(has_some_function<Y>::value); // correctly fails

为什么这不起作用,我将如何测试一个类是否有类型?

【问题讨论】:

    标签: c++ c++14 template-meta-programming sfinae typetraits


    【解决方案1】:

    我知道这个问题被标记为 C++14,但为了其他人登陆这里:

    在 C++20 及更高版本中,concepts 提供了一种简洁的语法(更不用说在许多情况下还不需要static_assertenable_if)来检查一个类型是否具有给定的类型成员:

    template<typename T>
    concept has_some_type = requires {
      typename T::some_type;
    };
    
    struct X {
      using some_type = int;
    };
    struct Y {};
    
    static_assert(has_some_type<X>); 
    static_assert(!has_some_type<Y>);
    

    【讨论】:

    • 是的。我确实喜欢概念。但不在卡片自动取款机中。
    【解决方案2】:

    为什么has_some_type trait 会失败?

    这是因为第二个模板参数的推导只使用主模板,而不是从您的模板专业化中“获取提示”。

    当您将X 替换为C 时,请考虑一下您对has_some_type 的部分专业化:

    template <>
    struct has_some_type<X, int> : std::true_type {};
    

    这个替换不会失败,但是 - 为什么它会匹配 has_some_type&lt;X&gt;?当编译器看到has_some_type&lt;X&gt;时,它会无情地忽略你试图引起它注意的尝试,而直接使用= void来推导第二个模板参数。

    为什么has_some_function trait 会成功?

    查看您在专业化中使用的第二种类型: decltype(std::declval&lt;C&gt;().some_function())。对于X,该类型解析为什么? ...是的,它解析为void。因此,当您将X 替换为C 时,它是:

    template <>
    struct has_some_function<X, void> : std::true_type {};
    

    并且这个匹配has_some_function&lt;X&gt;,使用主模板中的= void

    我们如何修复has_some_type trait?

    虽然 C++17 和 C++20 提供了更简单的解决方案(正如 @Frank 和 @Quimby 所建议的那样) - 让我们坚持使用 C++14 并考虑修复我们已经编写的内容。子>

    has_some_function 示例中,我们可能会受到启发,通过用template &lt;typename T, typename = int&gt; 替换主模板来“修复”类型特征;但是,虽然这适用于 XY,但如果您有 using some_type = double 或任何其他非 int 类型,它将不起作用。这不是我们想要的:-(

    那么,我们是否可以有一个类型,其定义是:“检查some_type 的有效性,不管它是什么,但最终表现得像一个单一的固定类型”? ...是的,事实证明我们可以。

    #include <type_traits>
    
    struct X {
      using some_type = int;
    };
    struct Y {};
    
    template<typename T>
    using void_t = void;
        // Please Mr. Compiler, make sure T is a legit type, then use void.
    
    template<typename, typename = void>
    struct has_some_type : std::false_type { };
     
    template<typename T>
    struct has_some_type<T, void_t<typename T::some_type>> : std::true_type { };
    
    static_assert(has_some_type<X>::value, "Unfortunately, X doesn't have some_type");
    static_assert(has_some_type<Y>::value, "Unfortunately, Y doesn't have some_type");
    

    只有第二个断言失败。

    GodBolt 上查看此操作。


    注意事项:

    • 此解决方案实际上是有效的 C++11。
    • C++17 引入了std::void_t 以节省您的打字时间。它还支持可变参数包,而不仅仅是单一类型。
    • 如果你使用旧的编译器,上面的代码可能会因为C++ standard defect CWG1558而失败。

    【讨论】:

    • 缺陷,CWG 1558
    • 谢谢。解释得很好。
    【解决方案3】:

    您错误地使用了void_t 成语。正确的做法是:

    template <typename C>
    struct has_some_type<C, std::void_t<typename C::some_type>> : std::true_type {};
    
    static_assert(has_some_type<X>::value); 
    static_assert(!has_some_type<Y>::value);
    

    对于 C++14,将void_t 定义为:

    template<typename T> struct void_impl { using type=void;};
    template<typename T> using void_t = typename void_impl<T>::type;
    

    概括一下成语:

    1. has_some_type&lt;X&gt; 使用从基本模板到 has_some_type&lt;X,void&gt; 的默认参数完成。
    2. 编译器会尝试为 has_some_type&lt;X,void&gt; 查找所有匹配的特化。
      • 原始 - 考虑has_some_type&lt;C, typename C::some_type&gt; C 可以推导出为XX::some_type 有效但不是void 类型,所以特化与使用的主模板不匹配。
      • 固定 - 考虑has_some_type&lt;C, std::void_t&lt;typename C::some_type&gt;&gt;,可以再次推导出C,这次std::void_t&lt;typename C::some_type&gt;void类型的有效表达式。这匹配并被认为比主模板更专业,因此被选中。

    意思是,您总是希望参数中的表达式计算为默认类型。该表达式巧妙地包含了您要测试的语法。

    第二个例子:

    struct has_some_function<C, std::void_t<decltype(std::declval<C>().some_function())>> 
    

    Cast to void 在这种情况下也可以使用:

    <C, decltype((void)std::declval<C>().some_function())>
    

    【讨论】:

    • 这是一个 C++17 解决方案...
    • @einpoklum 谢谢,我应该开始阅读标签了。给我一秒钟。
    • 你也参考了OP对has_f()的使用,而失败的代码是has_some_type
    • @einpoklum 但has_f 仅适用于函数返回void,我认为这不是OP 的意思。不是has_f,更像has_f_returning_void
    • 我的意思是,你的标识符混淆了。在第 1 步中您谈论 has_f,然后在第 2 步中间您切换到 has_some_type
    【解决方案4】:

    你可以使用 type_traits:

    #include <experimental/type_traits>
    
    struct X { using some_type = int; };
    struct Y {};
    
    
    template <typename T>
    using has_some_type_detector = T::some_type;
    
    static_assert( std::experimental::is_detected_v <has_some_type_detector, X> );
    static_assert( !std::experimental::is_detected_v <has_some_type_detector, Y> );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-31
      • 2011-02-27
      • 2014-05-07
      • 1970-01-01
      • 2022-01-23
      • 1970-01-01
      • 2015-02-13
      相关资源
      最近更新 更多