我一直在玩定义中的推断返回类型,它们解析为与声明相同的类型 . 这有效:
template <typename>
struct Cls {
static std::size_t f();
};
template <typename T>
decltype(sizeof(int)) Cls<T>::f() { return 0; }
但是,如果我将定义更改为应该等效的东西,将 sizeof(int)
替换为 sizeof(T)
it fails
template <typename T>
decltype(sizeof(T)) Cls<T>::f() { return 0; }
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();
^
函数参数类型出现同样的问题:
template <typename>
struct Cls {
static void f(std::size_t);
};
template <typename T>
void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead
更奇怪的是,如果声明和定义匹配并且都使用 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())>{}, "");
用另一个例子更新 . 这不是依赖类型,但仍然失败 .
template <int I>
struct Cls {
static int f();
};
template <int I>
decltype(I) Cls<I>::f() { return I; }
如果我在定义和声明中使用 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; }
更新3:M.M.的另一个失败的例子 . 非模板类的模板化成员函数 .
struct S {
template <int N>
int f();
};
template <int N>
decltype(N) S::f() {}
为什么声明和定义只是不同意依赖类型是非法的?即使类型本身不依赖于 template <int I>
,它为什么会受到影响?
1 回答
因为当涉及模板参数时,
decltype
根据标准返回unqiue依赖类型,见下文 . 如果没有模板参数,那么它会解析为明显的size_t
. 所以在这种情况下你必须选择声明和定义都有一个独立的表达式(例如size_t/decltype(sizeof(int))
),作为返回类型,或者两者都有依赖表达式(例如decltype(sizeof(T))
),它们解析为唯一的依赖类型并被认为是等价的,如果他们的表达是相同的(见下文) .在这篇文章中,我使用的是C标准草案N3337 .
这解释了什么是
decltype(sizeof(int))
. 但是对于decltype(sizeof(T))
,还有另一部分解释它是什么 .在Clang LLVM源文件中的3.9版
lib/AST/Type.cpp
这个重要的短语开头是“因此是一个类型......” . 它再次澄清了这种情况 .
再次在Clang源文件中的3.9版
lib/AST/ASTContext.cpp
所以你看到Clang收集并从特殊集中选择
decltype
中那些独特的依赖类型 .为什么编译器是如此愚蠢以至于它没有看到
decltype
的表达式sizeof(T)
始终是size_t
?是的,这对于人类读者来说是显而易见的 . 但是当你设计和实现一个正式的语法和语义规则时,特别是对于像C这样的复杂语言,你必须将问题分组并为它们定义规则,而不是仅针对每个特定问题提出规则,后者你不能用你的语言/编译器设计移动的方式 . 这里没有相同的规则:如果decltype
有一个函数调用表达式,不需要任何模板参数解析 - 将decltype
解析为函数的返回类型 . 除此之外,还有很多情况需要涵盖,你提出了一个更通用的规则,就像标准中引用的那样(14.4[2]
) .此外,AndyG在C-14中发现了一个类似的非显而易见的案例
auto
,decltype(auto)
(N4296,§7.1.6.4[dcl.spec.auto] 12/13):C 17中的变更,凭证编号> = N4582
自2016年3月起更改标准草案N4582(感谢bogdan)概括了以下声明:
此更改导致另一个描述类型相关表达式的部分,它对我们的特定情况看起来很奇怪 .
还有关于依赖于值的表达式的部分,如果依赖于
type-id
,sizeof
可以是值依赖的 . 值依赖表达式与decltype
之间没有关系 . 经过一番思考,我没有找到任何理由为什么decltype(sizeof(T))
一定不能或不能解析为size_t
. 而且我认为在编译器开发人员不太注意的标准中是非常偷偷摸摸的改变("involves a template parameter"到"type-dependent")(可能被许多其他变化所淹没,也许并不认为它可能实际上改变了某些东西,只是一个简单的配方改进) . 这种变化确实有意义,因为sizeof(T)
不依赖于类型,它取决于值 .decltype(e)
的操作数是一个未评估的操作数,即不关心值,只关于类型 . 这就是decltype
仅在e
依赖于类型时返回唯一类型的原因 .sizeof(e)
可能只是依赖于 Value 的 .我用clang 5,gcc 8
-std=c++1z
尝试了代码 - 结果相同:错误 . 我走得更远,尝试了这段代码:给出了相同的错误,即使
sizeof(sizeof(T))
既不依赖于类型也不依赖于值(参见this post) . 这让我有理由假设编译器以C-11/14标准(即"involves a template parameter")的旧方式工作,就像上面的clang 3.9源代码片段一样(我可以验证最新开发的clang 5.0具有相同的功能)行,没有找到与标准中的这个新变化相关的任何内容),但不依赖于类型 .