【问题标题】:A compile-type template predicate compiles with Clang, but not with GCC or MSVC编译类型的模板谓词使用 Clang 编译,但不能使用 GCC 或 MSVC
【发布时间】:2023-03-21 15:20:01
【问题描述】:

考虑以下 C++ 代码:

#include <type_traits>

template<int x, int y, bool visible>
struct Point { };

template<typename T>
struct Predicate : std::false_type { };

template<typename... Types, Types... Values, template<Types...> class Head>
struct Predicate<Head<Values...>> : std::true_type { };

static_assert(Predicate<Point<1, 2, true>>::value, "assert");

Predicate 的目的是识别具有任意模板类名(绑定到Head)的模板实例化,该模板实例化存储零个或多个非类型模板参数,可能是不同的类型,并且没有其他参数(类型或模板)。这段代码用 Clang 编译成功,但是 GCC 报错:

:10:29: 错误:扩展模式 '' 不包含 参数包

struct Predicate<Head<Values...>> : std::true_type { }; ^~~

MSVC 也抱怨:

(10): error C2764: 'Types': template parameter not used or 可在偏特化中推导出 'Predicate>'

哪个编译器是正确的,C++ 标准对这个话题有什么看法?


我注意到使用 C++17,GCC 的错误可以通过这种方式解决:

template<auto... Values, template<auto...> class Head>
struct Predicate<Head<Values...>> : std::true_type { };

但是这个版本还是有同样的错误:

template<auto... Values, template<decltype(Values)...> class Head>
struct Predicate<Head<Values...>> : std::true_type { };

【问题讨论】:

  • 我在使用 Visual Studio 时遇到过类似的问题,我设法通过将与您的 Predicate 等效的内容包装在另一个 struct 中来解决它,其中 struct 具有类型参数包并且实际的 Predicate 特征具有值参数。
  • template&lt;Types...&gt; typename Head - 这已经无效。 typename 在那里做什么?你的意思是template&lt;Types...&gt; class Head
  • @AnT 自 C++17 起,typename 可用于此上下文,与 class 同义。
  • 哦,好的。显然从 C++17 开始是允许的。我的立场是正确的。
  • 在 C++17 之前,模板参数在这种情况下是不可推导的:GCC 和 Clang 都同意这一点,即使我们去掉所有参数包并只用一个参数重写代码/参数在每个位置。在 C++17 模式下,两者都接受“无包”版本。但是使用参数包 GCC 确实拒绝编译它。

标签: c++ templates language-lawyer template-meta-programming


【解决方案1】:

GCC 和 MSVC 是正确的。

来自[temp.param]/19

如果一个 template-parameter 是一个 type-parameter,在其可选标识符之前有一个省略号,或者是一个声明的 parameter-declaration一个包 ([dcl.fct]),那么 template-parameter 是一个模板参数包。模板参数包是一个 parameter-declaration,其类型包含一个或多个未扩展的包是包扩展。类似地,一个模板参数包是一个 type-parameter 和一个 template-parameter-list 包含一个或多个未扩展包的包扩展。 作为包扩展的模板参数包不得扩展在同一模板参数列表中声明的模板参数包。 [ 示例:

template <class... Types>                       // Types is a template type parameter pack
   class Tuple;                                 // but not a pack expansion

template <class T, int... Dims>                 // Dims is a non-type template parameter pack
   struct multi_array;                          // but not a pack expansion

template <class... T>
  struct value_holder {
    template <T... Values> struct apply { };    // Values is a non-type template parameter pack
  };                                            // and a pack expansion

template <class... T, T... Values>              // error: Values expands template type parameter
  struct static_array;                          // pack T within the same template parameter list

— 结束示例 ]

【讨论】:

    【解决方案2】:

    似乎模板参数在这种情况下是不可推导出的。 您可能想尝试使用函数参数,似乎推导规则更容易使用它们。

    类似这样,如果您需要将输出作为类型,您可以将其包装在结构中:

    #include <type_traits>
    
    template<int x, int y, bool visible>
    struct Point { };
    
    template <typename T>
    struct NoPoint { };
    
    template <typename T>
    constexpr auto Match(T &&)
    {
        return false;
    }
    
    template<template < auto ... > typename Head, auto ... Values>
    constexpr auto Match(Head<Values...> &&)
    {
        return true;
    }
    
    static_assert(Match(Point<1, 2, true>{}), "I am a wombat.");
    static_assert(Match(NoPoint<int>{}), "I am also a wombat.");
    

    https://godbolt.org/g/fjofbx

    目前,上述内容仅限于具有默认构造函数的类型。 你们有一些可用的测试类型吗?

    编辑:

    一个改进的版本可能是,它不需要类型是默认可构造的:

    #include <type_traits>
    #include <utility>
    
    template<int x, int y, bool visible>
    struct Point { };
    
    template <typename T>
    struct NoPoint { };
    
    template <typename T>
    constexpr auto Match(T) -> std::false_type;
    
    template<template < auto ... > typename Head, auto ... Values>
    constexpr auto Match(Head<Values...>) -> std::true_type;
    
    template <typename T>
    struct Predicate : decltype(Match(std::declval<T>())) {};
    
    static_assert(Predicate<Point<1, 2, true>>::value, "I am a wombat.");
    static_assert(Predicate<NoPoint<int>>::value, "I am not a wombat.");
    

    https://godbolt.org/g/iru2mZ

    您还可以添加类似这样的内容以使其更实用:

    template <typename T>
    constexpr bool HasValuePack = Predicate<T>::value;
    

    编辑

    刚刚意识到我应该包含这个,带有模板变量的 C++17 版本,这是定义:

    //Predicate
    template <typename T>
    constexpr bool Predicate_v = false;
    template<template < auto ... > typename Head, auto ... Values>
    constexpr bool Predicate_v<Head<Values...>>   = true;
    

    这些测试用例

    //Test types
    template<int x, int y, bool visible>
    struct Point { };
    template <typename T>
    struct NoPoint { };
    
    //Test Cases
    static_assert(Predicate_v<Point<1, 2, true>>, "I am a wombat.");
    static_assert(Predicate_v<NoPoint<int>>, "I am not a wombat.");
    

    https://godbolt.org/g/c5gyhH

    【讨论】:

      猜你喜欢
      • 2020-06-03
      • 2014-05-16
      • 1970-01-01
      • 2022-10-18
      • 2018-04-20
      • 1970-01-01
      • 2022-10-12
      • 1970-01-01
      • 2017-10-17
      相关资源
      最近更新 更多