在未评估的上下文中是否允许对非常量静态数据成员进行内联初始化?
Is in-line initialization of a non-const static data member allowed in unevaluated contexts?
以下program
struct S
{
template <typename T>
static auto i = T{};
};
int main()
{
return decltype(S::i<int>){};
}
是GCC编译的,但是实例化S::i
时Clang报错:
error: non-const static data member must be initialized out of line
static auto i = T{};
^ ~~~
错误是有道理的,如果在计算上下文中使用 S::i
,GCC 仍会编译代码,但无法 link,因为它找不到 [=14] 的定义=].
由于 GCC 看到了足够多的 i
初始化程序来了解它的类型,但还不足以了解它的值,所以感觉像是一个 GCC 错误。另一方面,在未评估的上下文中,只需要类型,所以也许这没问题,这是一个 Clang 错误。
此代码有效吗?
TLDR
Is in line initialization of a non-const static data member allowed in unevaluated contexts?
Is this code valid?
没有也没有;这是错误的,因为 decltype(S::i<int>)
将需要 S::i
的类型来推导 int
特化,并且这需要 至少 实例化S::i<int>
的声明本身是错误格式的(我们可以通过显式实例化声明单独研究)。
然而,一个更有趣的问题是 decltype(S::i<int>)
是否也需要实例化 S::i<int>
的 定义 ;即使仅出于推导其类型的目的而不需要它。
详情
让我们先列举一下这里的内容:
S::i
正式是一个 静态数据成员模板 ([temp.def]/1.5)
S::i
根据其主要模板不是内联的;既没有明确使用 inline
关键字,也没有隐含地使用 constexpr
- 这也适用于它的所有特化,因为它没有明确的特化声明为内联 ([temp.expl.spec]/12)
这意味着 S::i
的 in-class 声明不是定义,因为它不属于 [class.static.data]/3:[=42 的任何特殊情况=]
[...] Declarations of other static data members shall not specify a brace-or-equal-initializer.
意味着它的声明不应包含初始化表达式。
The error makes sense, and if S::i
is used in an evaluated context, GCC still compiles the code, but fails to link as it doesn't find the definition of i
.
这就是它变得棘手的地方;为了简化一些让我们从等式中删除定义的“odr-need”,将您自己的示例修改为以下内容:
struct S
{
template <typename T>
static auto i = T{}; // #1
};
decltype(S::i<int>)* sp; // #2
// Clang: rejects-valid?
// GCC: accepts-invalid?
int main() {}
我们可以首先指出 任何 实例化,无论是显式还是隐式,即使只是 S::i
的声明也是格式错误的,这完全是由于以下事实与特化关联的声明 #1
(缺少可能用内联声明的显式特化)包含初始化表达式。
此外,
decltype(S::i<int>)* sp; // #2
需要从其初始化器中扣除 S::i<int>
,按照 [dcl.spec.auto]/4,但正如上面所指出的,这个初始化器的存在使得变量模板声明格式错误,所以仅此一项就应该足以拒绝该程序。然而,这并没有说明特化(或者特别是它的定义)是否真的需要实例化。
我们可能会注意到[dcl.spec.auto]/14指出显式实例化声明不会导致关联实体的实例化,但是
[...] it also does not prevent that entity from being instantiated as needed to determine its type.
如果我们相应地修改上面的例子:
struct S
{
template <typename T>
static auto i = T{}; // #1
};
extern template int S::i<int>;
int main() {}
Clang once 反对拒绝该程序,因为 #1 对于任何专业化都是错误的,而 GCC 接受它。 Clang 在这里是正确的,因为 auto
在变量模板的显式实例化声明中是不允许的,这意味着我们 必须 指定一个非推导类型,以及显式实例化的类型声明需要匹配给定专业化的主模板声明的推导类型(声明匹配);因此后者需要推断类型,这会在 #1
.
处触发错误构造
根据最后一个例子,GCC 显然有一个错误,至少对于我们只实例化给定专业化的(格式错误的)声明的情况。
然而,未求值的上下文是否会导致实体定义的隐式实例化,在某种程度上(正式地)仍未得到解答。这与以下问答重叠:
这暗示标准中可能存在关于“推导类型所需的隐式实例化”是否实际上也导致实体定义实例化的缺陷或规范不足(即使不是非正式地“需要”“根据要求").
以下program
struct S
{
template <typename T>
static auto i = T{};
};
int main()
{
return decltype(S::i<int>){};
}
是GCC编译的,但是实例化S::i
时Clang报错:
error: non-const static data member must be initialized out of line
static auto i = T{};
^ ~~~
错误是有道理的,如果在计算上下文中使用 S::i
,GCC 仍会编译代码,但无法 link,因为它找不到 [=14] 的定义=].
由于 GCC 看到了足够多的 i
初始化程序来了解它的类型,但还不足以了解它的值,所以感觉像是一个 GCC 错误。另一方面,在未评估的上下文中,只需要类型,所以也许这没问题,这是一个 Clang 错误。
此代码有效吗?
TLDR
Is in line initialization of a non-const static data member allowed in unevaluated contexts?
Is this code valid?
没有也没有;这是错误的,因为 decltype(S::i<int>)
将需要 S::i
的类型来推导 int
特化,并且这需要 至少 实例化S::i<int>
的声明本身是错误格式的(我们可以通过显式实例化声明单独研究)。
然而,一个更有趣的问题是 decltype(S::i<int>)
是否也需要实例化 S::i<int>
的 定义 ;即使仅出于推导其类型的目的而不需要它。
详情
让我们先列举一下这里的内容:
S::i
正式是一个 静态数据成员模板 ([temp.def]/1.5)S::i
根据其主要模板不是内联的;既没有明确使用inline
关键字,也没有隐含地使用constexpr
- 这也适用于它的所有特化,因为它没有明确的特化声明为内联 ([temp.expl.spec]/12)
这意味着 S::i
的 in-class 声明不是定义,因为它不属于 [class.static.data]/3:[=42 的任何特殊情况=]
[...] Declarations of other static data members shall not specify a brace-or-equal-initializer.
意味着它的声明不应包含初始化表达式。
The error makes sense, and if
S::i
is used in an evaluated context, GCC still compiles the code, but fails to link as it doesn't find the definition ofi
.
这就是它变得棘手的地方;为了简化一些让我们从等式中删除定义的“odr-need”,将您自己的示例修改为以下内容:
struct S
{
template <typename T>
static auto i = T{}; // #1
};
decltype(S::i<int>)* sp; // #2
// Clang: rejects-valid?
// GCC: accepts-invalid?
int main() {}
我们可以首先指出 任何 实例化,无论是显式还是隐式,即使只是 S::i
的声明也是格式错误的,这完全是由于以下事实与特化关联的声明 #1
(缺少可能用内联声明的显式特化)包含初始化表达式。
此外,
decltype(S::i<int>)* sp; // #2
需要从其初始化器中扣除 S::i<int>
,按照 [dcl.spec.auto]/4,但正如上面所指出的,这个初始化器的存在使得变量模板声明格式错误,所以仅此一项就应该足以拒绝该程序。然而,这并没有说明特化(或者特别是它的定义)是否真的需要实例化。
我们可能会注意到[dcl.spec.auto]/14指出显式实例化声明不会导致关联实体的实例化,但是
[...] it also does not prevent that entity from being instantiated as needed to determine its type.
如果我们相应地修改上面的例子:
struct S
{
template <typename T>
static auto i = T{}; // #1
};
extern template int S::i<int>;
int main() {}
Clang once 反对拒绝该程序,因为 #1 对于任何专业化都是错误的,而 GCC 接受它。 Clang 在这里是正确的,因为 auto
在变量模板的显式实例化声明中是不允许的,这意味着我们 必须 指定一个非推导类型,以及显式实例化的类型声明需要匹配给定专业化的主模板声明的推导类型(声明匹配);因此后者需要推断类型,这会在 #1
.
根据最后一个例子,GCC 显然有一个错误,至少对于我们只实例化给定专业化的(格式错误的)声明的情况。
然而,未求值的上下文是否会导致实体定义的隐式实例化,在某种程度上(正式地)仍未得到解答。这与以下问答重叠:
这暗示标准中可能存在关于“推导类型所需的隐式实例化”是否实际上也导致实体定义实例化的缺陷或规范不足(即使不是非正式地“需要”“根据要求").