函数定义中的decltype中的依赖类型或参数在没有decltype的情况下声明时无法编译

Rya*_*ing 28 c++ templates language-lawyer c++11

我一直在玩定义中的推断返回类型,它们解析为与声明相同的类型.这有效:

template <typename>
struct Cls {
  static std::size_t f();
};

template <typename T>
decltype(sizeof(int)) Cls<T>::f()  { return 0; }
Run Code Online (Sandbox Code Playgroud)

但是,如果我更改定义的东西,应该更换等同sizeof(int)sizeof(T) 失败

template <typename T>
decltype(sizeof(T)) Cls<T>::f() { return 0; }
Run Code Online (Sandbox Code Playgroud)

gcc的错误(clang几乎相同):

error: prototype for ‘decltype (sizeof (T)) Cls<T>::f()’ does not match any in class ‘Cls<T>’
 decltype(sizeof(T)) Cls<T>::f() { return 0; }
                     ^~~~~~
so.cpp:4:24: error: candidate is: static std::size_t Cls<T>::f()
     static std::size_t f();
                        ^
Run Code Online (Sandbox Code Playgroud)

函数参数类型出现同样的问题:

template <typename>
struct Cls {
  static void f(std::size_t);
};

template <typename T>
void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead
Run Code Online (Sandbox Code Playgroud)

更奇怪的是,如果声明和定义匹配并且使用decltype(sizeof(T))它成功编译,我可以static_assert返回类型size_t.以下编译成功:

#include <type_traits>

template <typename T>
struct Cls {
  static decltype(sizeof(T)) f();
};

template <typename T>
decltype(sizeof(T)) Cls<T>::f() { return 0; }

static_assert(std::is_same<std::size_t, decltype(Cls<int>::f())>{}, "");
Run Code Online (Sandbox Code Playgroud)

用另一个例子更新.这不是依赖类型,但仍然失败.

template <int I>
struct Cls {
  static int f();
};

template <int I>
decltype(I) Cls<I>::f() { return I; }
Run Code Online (Sandbox Code Playgroud)

如果我decltype(I)在定义和声明中使用它是有效的,如果我int在定义和声明中都使用它可以工作,但两个不同的失败.


更新2:一个类似的例子.如果Cls更改为不是类模板,则会成功编译.

template <typename>
struct Cls {
  static int f();
  using Integer = decltype(Cls::f());
};

template <typename T>
typename Cls<T>::Integer Cls<T>::f() { return I; }
Run Code Online (Sandbox Code Playgroud)

更新3:来自MM的另一个失败示例非模板化类的模板化成员函数.

struct S {
  template <int N>
  int f();
};

template <int N>
decltype(N) S::f() {}
Run Code Online (Sandbox Code Playgroud)

为什么声明和定义只是不同意依赖类型是非法的?即使类型本身不依赖于template <int I>上述内容,为什么它会受到影响?

Yuk*_*uki 9

因为当涉及模板参数时,decltype根据标准返回unqiue依赖类型,见下文.如果没有模板参数,那么它会解析为明显的size_t.因此,在这种情况下,你必须选择声明和定义都有一个独立的表达式(例如size_t/decltype(sizeof(int))),作为返回类型,或两者都有依赖表达式(例如decltype(sizeof(T))),它们解析为唯一的依赖类型并被认为是等效的,如果它们的表达式是等价的(见下文).

在这篇文章中,我使用的是C++标准草案N3337.

§7.1.6.2[dcl.type.simpl]

4

由decltype(e)表示的类型定义如下: - 如果e是未加密的id-expression或不加括号的类成员访问(5.2.5),则decltype(e)是由e命名的实体的类型.如果没有这样的实体,或者e命名了一组过载的功能,那么该程序就会形成错误;

- 否则,如果e是x值,则decltype(e)是T &&,其中T是e的类型;

- 否则,如果e是左值,则decltype(e)是T&,其中T是e的类型;

- 否则,decltype(e)是e的类型.

这解释了什么decltype(sizeof(int)).但是,decltype(sizeof(T))还有另一部分解释它是什么.

§14.4[temp.type]

2

如果表达式e涉及模板参数,则decltype(e)表示唯一的依赖类型.两个这样的decltype-specifiers只有在它们的表达式是等价的时才引用相同的类型(14.5.6.1).[注意:但是,它可能是别名,例如,通过typedef-name. - 结束说明]

在Clang LLVM源文件中的3.9版 lib/AST/Type.cpp

DecltypeType::DecltypeType(Expr *E, QualType underlyingType, QualType can)
  // C++11 [temp.type]p2: "If an expression e involves a template parameter,
  // decltype(e) denotes a unique dependent type." Hence a decltype type is
  // type-dependent even if its expression is only instantiation-dependent.
  : Type(Decltype, can, E->isInstantiationDependent(),
         E->isInstantiationDependent(),
         E->getType()->isVariablyModifiedType(),
         E->containsUnexpandedParameterPack()),
Run Code Online (Sandbox Code Playgroud)

这个重要的短语开头是"因此是一个类型......".它再次澄清了这种情况.

再次在Clang源文件中的3.9版本 lib/AST/ASTContext.cpp

QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const {
  DecltypeType *dt;

  // C++11 [temp.type]p2:
  //   If an expression e involves a template parameter, decltype(e) denotes a
  //   unique dependent type. Two such decltype-specifiers refer to the same
  //   type only if their expressions are equivalent (14.5.6.1).
  if (e->isInstantiationDependent()) {
    llvm::FoldingSetNodeID ID;
    DependentDecltypeType::Profile(ID, *this, e);

    void *InsertPos = nullptr;
    DependentDecltypeType *Canon
      = DependentDecltypeTypes.FindNodeOrInsertPos(ID, InsertPos);
    if (!Canon) {
      // Build a new, canonical typeof(expr) type.
      Canon = new (*this, TypeAlignment) DependentDecltypeType(*this, e);
      DependentDecltypeTypes.InsertNode(Canon, InsertPos);
    }
    dt = new (*this, TypeAlignment)
        DecltypeType(e, UnderlyingType, QualType((DecltypeType *)Canon, 0));
  } else {
    dt = new (*this, TypeAlignment)
        DecltypeType(e, UnderlyingType, getCanonicalType(UnderlyingType));
  }
  Types.push_back(dt);
  return QualType(dt, 0);
}
Run Code Online (Sandbox Code Playgroud)

所以你看到Clang收集并decltype从特殊集合中选择那些独特的依赖类型.

为什么编译器是愚蠢,不看到的表达decltypesizeof(T),始终是size_t?是的,这对于人类读者来说是显而易见的.但是当你设计和实现一个正式的语法和语义规则时,特别是对于像C++这样的复杂语言,你必须将问题分组并为它们定义规则,而不是仅针对每个特定问题提出规则,后者你不能用你的语言/编译器设计移动的方式.这里没有相同的规则:如果decltype有一个函数调用表达式,不需要任何模板参数解析 - 解析decltype为函数的返回类型.除此之外,您需要涵盖的案例很多,您提出了一个更通用的规则,如上面引用的标准(14.4[2]).

另外,一个类似的非显而易见的案例auto,decltype(auto)由AndyG在C++ - 14中找到(N4296,§7.1.6.4[dcl.spec.auto] 12/13):

§7.1.6.4[dcl.spec.auto]

13

具有使用占位符类型的声明返回类型的函数或函数模板的重新声明或特化也应使用该占位符,而不是推导类型.[例如:

auto f();
auto f() { return 42; } // return type is int
auto f();               // OK
int f();                // error, cannot be overloaded with auto f()
decltype(auto) f();     // error, auto and decltype(auto) don’t match
Run Code Online (Sandbox Code Playgroud)

C++中的变化17,文档编号> = N4582

自2016年3月起更改标准草案N4582(感谢bogdan)概括了以下声明:

§17.4(旧§14.4)[temp.type]

2

如果表达式e是类型依赖的(17.6.2.2),则decltype(e)表示唯一的依赖类型.两个这样的decltype-specifiers只有在它们的表达式是等价的时才引用相同的类型(17.5.6.1).[注意:但是,这种类型可能是别名,例如,通过typedef-name. - 结束说明]

此更改导致另一个描述类型相关表达式的部分,它对我们的特定情况看起来很奇怪.

§17.6.2.2[temp.dep.expr](旧§14.6.2.2)

4

以下表单的表达式从不依赖于类型(因为表达式的类型不能依赖):

...
sizeof ( type-id )
...
Run Code Online (Sandbox Code Playgroud)

关于依赖于值的表达式还有其他部分,sizeof如果type-id依赖,则可以依赖于值.依赖于价值的表达与之间没有关系decltype.经过一番思考,我没有找到任何理由为何decltype(sizeof(T))不能或不能解决size_t.而且我认为在编译器开发人员不太注意的标准中(这可能是不可能被其他许多变化所淹没,也许并不认为它可能会让人感到不知所措)这是一个非常偷偷摸摸的变化("涉及模板参数"到"类型依赖")实际上改变了一些东西,只是简单的改进配方).这种变化确实有意义,因为sizeof(T)它不依赖于类型,而且取决于价值.decltype(e)的操作数是一个未评估的操作数,即不关心价值,只涉及类型.这就是为什么decltype仅在e类型相关时返回唯一类型.sizeof(e)可能只是依赖于价值.

我用clang 5,gcc 8尝试了代码-std=c++1z- 结果相同:错误.我走得更远,尝试了这段代码:

template <typename>
struct Cls {
  static std::size_t f();
};

template <typename T>
decltype(sizeof(sizeof(T))) Cls<T>::f() {
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

给出了相同的错误,即使这sizeof(sizeof(T))既不依赖于类型也不依赖于值(参见本文).这让我有理由假设编译器正在以旧的方式使用C++ - 11/14标准(即"涉及模板参数"),就像上面的clang 3.9源代码片段一样(我可以验证最新的开发clang 5.0具有相同的行,没有找到与标准中的这个新变化相关的任何内容),但不依赖于类型.

  • @AndyG `sizeof(T)` 是否涉及模板参数?是的。答案中引用了标准:“如果表达式 e 涉及模板参数,则 decltype(e) 表示唯一的依赖类型。” 对于了解 C++ 的人来说,显然 `sizeof` 总是返回 `size_t` 并且问题得到了解决。但是,当您编写正式的语法和语义规则时,不仅仅有这种简单的规则。 (2认同)
  • @Yuki:谢谢,这清除了它.我认为更多的直觉可以在C++ 14标准N4296 [dcl.spec.auto]/13(以及部分12)中找到,它说明"具有使用占位符的声明返回类型的函数或函数模板的重新声明或特化type也应使用该占位符,而不是推断类型." 直觉是`decltype(sizeof(T))`是一个占位符类型,直到模板被实例化([dcl.spec.auto]/12),并且编译器已经可以看到你混合了一个显式类型和一个占位符.以这种方式思考它会有所帮助,尽管它有点不精确 (2认同)
  • @Yuki*"作为一个知道C++的人,你很明显,sizeof总是返回size_t,问题就解决了."*我不太确定这句话 - `decltype(sizeof(T))`for`T = void`应该默默地强迫sfinea'ing ... [例子](https://wandbox.org/permlink/WtuMBtWCckhn32ux) (2认同)
  • 请注意,您从[temp.type]引用的段落已被[DR 2064](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#2064)的决议更改,已于2016年3月纳入工作草案(N4582).表达式现在必须依赖于类型. (2认同)