【问题标题】:conversion operator which refers to class template parameters and constexpr class members引用类模板参数和 constexpr 类成员的转换运算符
【发布时间】:2019-06-09 10:42:22
【问题描述】:
#include <cstddef>

template <class T, std::size_t rank_>
struct  B { };

template <class T, std::size_t rank_>
struct  A {
    static constexpr auto rank = rank_;
    operator B<T, rank>() noexcept;
};

template <class T, std::size_t rank>
A<T, rank>::operator B<T, rank>() noexcept { return {}; }

注意rank_A 的类模板参数,rankA 的成员的编译时常量。

  1. rank用于声明转换运算符

    • g++ 和 clang 编译没有错误。
    • MSVC 19.20 提供unable to match definition to an existing declaration
  2. rank_用于转换运算符的声明

    • 声明从operator B&lt;T, rank&gt;() noexcept;更改为operator B&lt;T, rank_&gt;() noexcept;
    • g++ 给no declaration matches A&lt;T, rank&gt;::operator B&lt;T, rank&gt;
    • clang 给out-of-line definition of operator B&lt;type-parameter-0-0, rank&gt; does not match any declaration in A&lt;T, rank_&gt;
    • MSVC 编译没有错误

  1. 谁是正确的?
  2. 什么是解决问题的便携式解决方案?

感谢 Artyer,将运算符定义中的符号名称从 rank 更改为 rank_ 解决了这个问题。这可能是由于名为rank 的模板参数和类成员rank 之间的歧义所致。编译器执行名称查找的方式不同。

天箭链接:https://godbolt.org/z/6oFdrf

【问题讨论】:

  • 奇怪的是,如果您在 gcc (template&lt;class T, std::size_t rank_&gt; A&lt;T, rank_&gt;::operator B&lt;T, rank_&gt;() noexcept { return {}; }) 中将外部定义更改为使用名称 rank_,它会起作用
  • @Artyer 保险杠!它也适用于 MSVC,但更改符号名称会阻止错误非常奇怪。不知道重载中rank成员和rank模板参数是否有冲突。
  • 这很可能是符号rank的冲突。对于A&lt;T, rank&gt;:: 部分,rank 必须是模板参数,因为编译器还不知道成员变量。但是对于::operator B&lt;T, rank&gt;(),那么rank 可以是模板参数成员变量。

标签: c++ visual-c++ g++ clang language-lawyer


【解决方案1】:

谁是正确的?

Clang 和 GCC 在所有帐户上都是正确的。您更改后的定义格式不正确的原因是子句的有趣混合。我会先命名,然后再解释。

[temp.local]

7在一个类模板的成员定义中出现 在类模板定义之外, 类模板隐藏任何封闭的模板参数的名称 类模板(但不是成员的模板参数,如果 member 是一个类或函数模板)。 [ 示例:

template<class T> struct A {
  struct B { /* ... */ };
  typedef void C;
  void f();
  template<class U> void g(U);
};

template<class B> void A<B>::f() {
  B b;              // A's B, not the template parameter
}

template<class B> template<class C> void A<B>::g(C) {
  B b;              // A's B, not the template parameter
  C c;              // the template parameter C, not A's C
}

 — 结束示例 ]

[temp.over.link]

4 使用引用模板参数的表达式时 在函数参数列表或声明中的返回类型中 函数模板的,引用模板的表达式 参数是函数模板签名的一部分。这是 必须允许在一个函数模板中声明 要与函数的另一个声明链接的翻译单元 另一个翻译单元中的模板,相反,以确保 旨在区分的功能模板未链接 彼此。 [ 示例:

template <int I, int J> A<I+J> f(A<I>, A<J>);   // #1
template <int K, int L> A<K+L> f(A<K>, A<L>);   // same as #1
template <int I, int J> A<I-J> f(A<I>, A<J>);   // different from #1

 — 结束示例 ] [ 注意:大多数表达式使用模板参数 使用非类型模板参数,但可以用于表达式 引用类型参数。例如,模板类型参数 可用于 sizeof 运算符。 — 尾注 ]

所以在opertor B&lt;T, rank&gt;声明中,id-expression rank (A&lt;T, rank_&gt;::rank) 是运算符签名的一部分,因为它用于返回类型(a转换运算符的返回类型作为其名称的一部分隐式给出),并且它引用了一个模板参数。

当您将operator B&lt;T, rank&gt;() noexcept; 更改为operator B&lt;T, rank_&gt;() noexcept; 时,您更改了操作员的签名!现在类外定义不匹配了,因为您仍然尝试在签名中使用 id-experssion rank,因为类成员在 :: 之后隐藏了模板参数。

template <class T, std::size_t rank>
A<T, rank>::operator B<T, rank>() noexcept { return {}; }
                        // ^- This is the member of A, not the name 
                        //    of the parameter above it

什么是解决问题的便携式解决方案?

不使用rank 成员作为模板参数或参数名称,而是在任何地方选择rank_,允许您问题中的三个编译器接受代码。

【讨论】:

    猜你喜欢
    • 2019-01-14
    • 2016-08-27
    • 1970-01-01
    • 2016-03-01
    • 1970-01-01
    • 2015-07-22
    • 2020-07-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多