简体   繁体   English

在没有decltype的情况下声明时,函数定义中decltype中的从属类型或自变量无法编译

[英]Dependent type or argument in decltype in function definition fails to compile when declared without decltype

I've been playing with deduced return types in definitions that resolve to the same type as the declaration. 我一直在使用推定的返回类型定义来解析为与声明相同的类型。 This works: 这有效:

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

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

But if I change the definition to something that should be equivalent by replacing sizeof(int) with sizeof(T) it fails 但是如果我通过将sizeof(int)替换为sizeof(T)来将定义更改为应该等效的内容, 它将失败

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

gcc's error (clang is almost identical): gcc的错误(叮当声几乎相同):

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();
                        ^

The same problem arises with function parameter types: 函数参数类型也会出现相同的问题:

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

template <typename T>
void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead

Stranger yet, if the declaration and definition match and both use decltype(sizeof(T)) it compiles successfully, and I can static_assert that the return type is size_t . 奇怪的是,如果声明和定义匹配并且使用decltype(sizeof(T))则编译成功,并且我可以static_assert声明返回类型为size_t The following compiles successfully: 以下将成功编译:

#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())>{}, "");

Update with another example. 用另一个示例进行更新。 This isn't a dependent type but still fails. 这不是从属类型,但仍然会失败。

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

template <int I>
decltype(I) Cls<I>::f() { return I; }

If I use decltype(I) in both the definition and declaration it works, if I use int in both the definition and declaration it works, but having the two differ fails. 如果在定义和声明中都使用decltype(I) ,则可以使用;如果在定义和声明中都使用int ,则可以使用,但是使两者不同会失败。


Update 2: A similar example. 更新2:一个类似的示例。 If Cls is changed to not be a class template, it compiles successfully. 如果将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; }

Update 3: Another failing example from MM A templated member function of a non-templated class. 更新3:另一个来自MM的失败示例非模板类的模板成员函数。

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

template <int N>
decltype(N) S::f() {}

Why is it illegal for the declaration and definition to disagree with a dependent type only? 为什么声明和定义仅与从属类型不同是非法的? Why is it affected even when the type itself isn't dependent as with the template <int I> above? 即使类型本身与上面的template <int I>不相关,它为什么也会受到影响?

Because when there is a template parameter involved, decltype returns an unqiue dependent type according to the standard, see below. 因为当涉及到模板参数时, decltype根据标准返回不依赖于语言的类型,请参见下文。 If there is no template parameter then it resolves to an obvious size_t . 如果没有模板参数,则解析为明显的size_t So in this case you have to choose either both declaration and definition have an independent expression (eg size_t/decltype(sizeof(int)) ), as a return type, or both have dependent expression (eg decltype(sizeof(T)) ), which resolved to an unique dependent type and considered to be equivalent, if their expressions are equivalent (see below). 因此,在这种情况下,您必须选择声明和定义都具有独立的表达式(例如size_t/decltype(sizeof(int)) )作为返回类型,或者两者都具有从属表达式(例如decltype(sizeof(T)) ) ,如果它们的表达式等效,则解析为唯一的依赖类型,并视为等效(请参见下文)。

In this post I am using the C++ standard draft N3337 . 在这篇文章中,我正在使用C ++标准草案N3337

§ 7.1.6.2 [dcl.type.simpl] 第7.1.6.2节[dcl.type.simpl]

¶ 4 ¶4

The type denoted by decltype(e) is defined as follows: — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. 用decltype(e)表示的类型定义如下:—如果e是未括号化的id表达式或未括号化的类成员访问(5.2.5),则decltype(e)是e命名的实体的类型。 If there is no such entity, or if e names a set of overloaded func- tions, the program is ill-formed; 如果没有这样的实体,或者如果e命名了一组重载函数,则程序格式错误;

— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e; —否则,如果e为x值,则decltype(e)为T &&,其中T为e的类型;

— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e; —否则,如果e为左值,则decltype(e)为T&,其中T为e的类型;

— otherwise, decltype(e) is the type of e . —否则, decltype(e)是e的类型

This explains what is decltype(sizeof(int)) . 这解释了什么是decltype(sizeof(int)) But for decltype(sizeof(T)) there is another section explaining what it is. 但是对于decltype(sizeof(T))还有另一节说明了它是什么。

§ 14.4 [temp.type] 第14.4节[临时类型]

¶ 2 ¶2

If an expression e involves a template parameter, decltype(e) denotes a unique dependent type . 如果表达式e包含模板参数,则decltype(e)表示唯一的从属类型 Two such decltype-specifiers refer to the same type only if their expressions are equivalent (14.5.6.1). 两个这样的decltype-specifier仅在它们的表达式相等时才引用同一类型(14.5.6.1)。 [ Note: however, it may be aliased, eg, by a typedef-name. [注意:但是,可以使用typedef-name作为别名。 — end note ] —尾注]

In Clang LLVM sources version 3.9 in file lib/AST/Type.cpp 在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()),

The important phrase starts as "Hence a decltype...". 重要的短语开头为“因此有一个十进制类型...”。 It again clarifies the situation. 它再次澄清了这种情况。

Again in Clang sources version 3.9 in file lib/AST/ASTContext.cpp 同样在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);
}

So you see Clang gathers and picks those unique dependent types of decltype in/from a special set. 因此,您会看到Clang收集并从特殊集合中选择了那些独特的decltype相关类型。

Why compiler is so stupid that it does not see that the expression of decltype is sizeof(T) that is always size_t ? 为什么编译器如此愚蠢,以至于看不到decltype的表达式是始终为size_t sizeof(T) Yes, this is obvious to a human reader. 是的,这对于人类读者来说是显而易见的。 But when you design and implement a formal grammar and semantic rules, especially for such complicated languages as C++, you have to group problems up and define the rules for them, rather than just come up with a rule for each particular problem, in the latter way you just wont be able to move with your language/compiler design. 但是,当您设计和实现正式的语法和语义规则时,尤其是对于诸如C ++这样的复杂语言,则必须将问题归类并为其定义规则,而不是仅仅针对每个特定问题提出一个规则。您将无法使用您的语言/编译器设计的方式。 The same here there is no just rule: if decltype has a function call expression that does not need any template parameters resolution - resolve decltype to the return type of the function. 此处没有相同的规则,那就是:如果decltype具有不需要任何模板参数解析的函数调用表达式-将decltype解析为函数的返回类型。 There is more than that, there are so many cases you need to cover, that you come up with a more generic rule, like the quoted above from the standard ( 14.4[2] ). 不仅如此,您还需要处理很多情况,以至于想出了一个更通用的规则,例如上述标准( 14.4[2] )中引用的规则。

In addition, a similar non-obvious case with auto , decltype(auto) found by AndyG in C++-14 (N4296, § 7.1.6.4 [dcl.spec.auto] 12/13): 此外,AndyG在C ++-14(N4296,第7.1.6.4节[dcl.spec.auto] 12/13)中发现了与autodecltype(auto)类似的非显而易见的情况:

§ 7.1.6.4 [dcl.spec.auto] §7.1.6.4 [dcl.spec.auto]

¶ 13 ¶13

Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type. 具有使用占位符类型的已声明返回类型的函数或函数模板的重新声明或特化也应使用该占位符,而不是推导类型。 [ Example: [示例:

 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 

Changes in C++17, Document Number >= N4582 C ++ 17中的更改,文档编号> = N4582

Change in the standard draft N4582 from March 2016 (thanks to bogdan) generalizes the statement: 自2016年3月起对标准N4582草案进行了更改(感谢bogdan),概括了以下说法:

§ 17.4 (old § 14.4) [temp.type] §17.4(旧的§14.4)[临时类型]

¶ 2 ¶2

If an expression e is type-dependent (17.6.2.2) , decltype(e) denotes a unique dependent type. 如果表达式e是依赖类型的(17.6.2.2) ,则decltype(e)表示唯一的依赖类型。 Two such decltype-specifiers refer to the same type only if their expressions are equivalent (17.5.6.1). 两个这样的decltype-specifier仅在它们的表达式相等时才引用同一类型(17.5.6.1)。 [ Note: however, such a type may be aliased, eg, by a typedef-name. [注意:但是,可以使用例如typedef-name作为别名的类型。 — end note ] —尾注]

This change leads to another section describing the type dependent expression that looks quite strange to our particular case. 此更改导致另一部分描述了类型依赖表达式,该表达式对于我们的特定情况看起来很奇怪。

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

¶ 4 ¶4

Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent): 以下形式的表达式从不依赖类型(因为表达式的类型不能依赖):

 ... sizeof ( type-id ) ... 

There are further sections on value-dependent expressions where sizeof can be value-dependent if the type-id dependent. 还有一些关于值相关表达式的部分,其中如果type-id相关,则sizeof可以取决于值。 There is no relation between value-dependent expression and decltype . 与值相关的表达式和decltype之间没有关系。 After some thinking, I did not find any reason why decltype(sizeof(T)) must not or cannot resolve into size_t . 经过一番思考,我没有找到decltype(sizeof(T))一定不能或不能解析为size_t任何原因。 And I would assume that was quite sneaky change ("involves a template parameter" to "type-dependent") in the standard that compiler developers did not pay much attention to (maybe overwhelmed by many other changes, maybe did not think that it might actually change something, just a simple formulation improvement). 而且我认为这是编译器开发人员没有非常注意的标准(可能被许多其他更改淹没,也许没有认为它可能会是标准)中的一个相当偷偷摸摸的更改(“将模板参数”涉及“类型相关”)。实际改变一些东西,只是简单的配方改进)。 The change does make sense, because sizeof(T) is not type-dependent, it is value-dependent. 更改确实有意义,因为sizeof(T)不依赖于类型,而是依赖于值。 decltype(e) 's operand is a unevaluated operand, ie does not care about value, only about type. decltype(e)的操作数是未评估的操作数,即不在乎值,仅在乎类型。 That is why decltype returns a unique type only when e is type-dependent. 这就是为什么decltype仅在e与类型相关时才返回唯一类型。 sizeof(e) might be only value-dependent. sizeof(e)可能仅取决于值。

I tried the code with clang 5, gcc 8 -std=c++1z - the same result: error. 我尝试使用clang 5,gcc 8 -std=c++1z的相同结果:错误。 I went further and tried this code: 我进一步尝试了以下代码:

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

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

The same error was given, even that sizeof(sizeof(T)) is neither type- or value-dependent (see this post ). 给出了相同的错误,即使sizeof(sizeof(T))既不依赖类型也不依赖于值(请参见此文章 )。 This gives me a reason to assume that the compilers are working in an old way of C++-11/14 standard (ie "involves a template parameter") like in the source snippet above from clang 3.9 source (I can verify that the latest developing clang 5.0 has the same lines, have not found anything related to this new change in the standard), but not type-dependent. 这给了我一个理由来假设编译器正在按照C ++-11/14标准的旧方式工作(例如,“涉及模板参数”),就像上面clang 3.9源代码中的代码片段一样(我可以验证最新的开发者clang 5.0具有相同的内容,没有在标准中找到与此新更改相关的任何内容,但与类型无关。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM