【问题标题】:Failed template argument deduction with partially specialised template using 'auto'使用“auto”的部分专用模板的模板参数推导失败
【发布时间】:2019-10-28 18:36:09
【问题描述】:

为了好玩,我创建了一个非常基本的编译时类型-值映射类,如下:

template <typename T, auto V>
struct TypeValuePair { };

template <typename... TypeValuePairs>
struct TypeValueMap
  {
  struct MapItems : TypeValuePairs... { };

  template <typename T, auto V>
  static constexpr auto Lookup(TypeValuePair<T, V>*)
    { return V; }

  template <auto V, typename T>
  static T Lookup(TypeValuePair<T, V>*);

  template <typename T>
  static constexpr auto ValueFor = Lookup<T>((MapItems*)nullptr);

  template <auto V>
  using TypeFor = decltype(Lookup<V>((MapItems*)nullptr));
  };

以这样的方式使用:

struct A; struct B; struct C;
enum class Values { A, B, C };

using Map = TypeValueMap<
                TypeValuePair<A, Values::A>,
                TypeValuePair<B, Values::B>,
                TypeValuePair<C, Values::C>,
                TypeValuePair<struct Other, 0>
              >;

static_assert(Map::ValueFor<A> == Values::A, "");
static_assert(Map::ValueFor<B> == Values::B, "");
static_assert(Map::ValueFor<C> == Values::C, "");
static_assert(Map::ValueFor<struct Other> == 0, "");

static_assert(std::is_same<Map::TypeFor<Values::A>, A>::value, "");     //***
static_assert(std::is_same<Map::TypeFor<Values::B>, B>::value, "");
static_assert(std::is_same<Map::TypeFor<Values::C>, C>::value, "");
static_assert(std::is_same<Map::TypeFor<0>, struct Other>::value, "");  //***

不幸的是,标记为//*** 的两行在clang 和g++(我必须使用的两个编译器)上出现错误模板参数推导失败 或类似错误。我可以理解为什么这可能是因为Values::A 的值是0,所以两者可能会发生冲突。但是,我认为它们实际上是不同的类型——一种是纯整数,另一种是enum class,具有底层整数类型——因此实际上不应该发生冲突。

如果我以不同的方式实现我的地图类,如下所示:

template <typename T, auto V>
struct TypeValuePair
  {
  protected:
  static constexpr auto Lookup(T*)
    { return V; }

  template <template <auto> class Wrapper>
  static T Lookup(Wrapper<V>*);
  };

template <typename... TypeValuePairs>
struct TypeValueMap
  {
  struct MapItems : TypeValuePairs...
    { using TypeValuePairs::Lookup...; };

  template <auto> struct LookupByValue;

  template <typename T>
  static constexpr auto ValueFor = MapItems::Lookup((T*)nullptr);

  template <auto V>
  using TypeFor = decltype(MapItems::Lookup((LookupByValue<V>*)nullptr));
  };

那么就不存在模板参数推导错误了。

因此问题是,由于编译器中的错误(假设我断言整数和enum class 应该被视为不同的类型而不是冲突)而未能在第一个实现中推断出模板参数,还是它我对模板参数推导可能发生的事情有误解(我不是语言律师!),还是我的实现中存在其他错误?

【问题讨论】:

  • 与主要问题无关,但您可以简化:struct MapItems : TypeValuePairs... { using TypeValuePairs::Lookup...; };.
  • 你确定clang和gcc会产生同样的错误吗?如果从映射中删除 A,两个编译器会发生什么情况?
  • 感谢@Evg 的建议,这真是太好了!我已通过此更改更新了问题。
  • @n.m. - 是的,两个编译器都产生相同的错误;如果您从地图中删除 A,则“歧义”消失并且剩余地图解决
  • 最小示例:godbolt.org/z/nIFKxY

标签: c++


【解决方案1】:

问题在于Values::A 可以通过“转换后的常量表达式”转换为0

来自 C++14 标准(n4140 第 5.19 节,第 3 段,第 133 页):

转换后的 T 类型常量表达式是一个表达式,隐式转换为 T 类型的纯右值,其中转换后的表达式是核心常量表达式,并且隐式转换序列仅包含用户定义的转换、左值到右值的转换(4.1)、积分提升 (4.5) 和积分转换 (4.7),而不是缩小转换 (8.5.4)。

[ 注意:此类表达式可用于新表达式 (5.3.4)、case 表达式 (6.4.2)、如果基础类型固定 (7.2) 则用作枚举数初始化器、数组边界 (8.3.4) ,以及作为整数或枚举非类型模板参数 (14.3)。 ——尾注]

(我的重点)

效果是0Values::A 之间存在重载歧义。

【讨论】:

  • 但是,当使用 'auto' 非类型模板参数执行歧义检查时,这是否正确?如果没有严格必要的转换,为什么编译器需要从中生成转换后的常量表达式?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-05
  • 2021-04-18
  • 2012-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多