包含有不规范模板成员函数的良构程序?

9
在下面的片段中,我对为什么Wrapper::f() const的定义没有使我的程序无效而感到困惑1,尽管它调用了一个非常量成员变量的非常量成员函数。
// well-formed program (???)
// build with: g++ -std=c++17 -Wall -Wextra -Werror -pedantic
template<class T> struct Data { void f() {} };

template<class T> struct Wrapper
{
    Data<T> _data;
    void f() const { _data.f(); } // _data.f(): non-const!
};

int main()
{
    Wrapper<void> w; // no error in instantiation point?
    (void) w;
}

演示2

另一方面,如果Data是一个非模板类3,我的编译器会发出诊断:

// ill-formed program (as expected)
// build with: g++ -std=c++17 -Wall -Wextra -Werror -pedantic
struct Data { void f() {} };

template<class T> struct Wrapper
{
    Data _data;
    void f() const { _data.f(); } //error: no matching function for call to 'Data::f() const'
};

int main()
{
    Wrapper<void> w;
    (void) w;
}

演示

我觉得答案会包含表达式“推断上下文”之类的内容...但我真的无法确定规范中具体指定这种行为的部分。

有没有专业人士能为我解开这个谜团?


注:
1) 但是,如果我尝试有效地调用Wrapper<T>::f() const,就会出现错误。
2) 我已经使用了-std=c++17进行编译,但这不是特定于C++17的,因此没有特定的标签。
3)这个答案中,@Baum mit Augen引用了[N4140, 14.7.1(2)]

当特化被引用在需要存在成员定义的上下文中时,成员的特化被隐式实例化

但是,在编译片段(#2)中,尽管其“特化从未在需要成员定义存在的上下文中引用”,但void f() const { _data.f(); }仍失败。


我认为重复内容并没有涵盖这个问题的全部。至少我希望第二个版本也能够按照重复内容中给出的答案进行编译。 - 463035818_is_not_a_number
1
请参见temp.res/8,特别是(8.1)。 - cpplearner
@cpplearner 8.1 是关于 constexpr if 的内容... - YSC
1
@YSC 这是关于“模板或是位于模板的constexpr if语句中的子语句”的问题,不过这就是我所理解的全部内容了。标准文件可不是我能理解的语言 :( - 463035818_is_not_a_number
@tobi303 哦,是的,我读过“无法为constexpr if的模板或子语句生成有效的特化”,而不是“无法为模板或constexpr if的子语句生成有效的特化”;但这没有意义:D 是我的错。 - YSC
显示剩余4条评论
1个回答

4

代码片段#2不符合规范。

正如这个回答中已经说明的,只要可以生成有效的特化,Wrapper :: f 的模板定义就是良好的(因此不会发出任何诊断)。

§17.7/8 [temp.res]表明:

了解哪些名称是类型名称允许检查每个模板的语法。如果:

  • 不能为模板或constexpr if语句中的子语句生成有效的特化,并且未实例化该模板,则程序无法形成,无需诊断,或[...]

在两个代码片段中,都未实例化Wrapper<void> :: f ,因为根据§17.7.1 / 2 [temp.inst] 的规则,:

类模板特化的隐式实例化导致声明的隐式实例化,但不导致定义的隐式实例化[...]。

(强调我做的)

但现在§17.7/8生效:如果没有实例化,并且无法生成模板定义的有效特化,则程序无法形成(对于代码片段#2是这种情况,因为对于每个生成的特化Wrapper<T> :: f ,在const 函数中执行了一个non-const 调用),并发出诊断。

但是由于诊断不是强制性的(请参见上面的§17.7/8),因此GCC可以拒绝代码片段#2,而VSclang都能完美编译相同的代码。

然而,对于代码片段#1,您可以为Data 提供用户定义的特化,其中Data :: f const (例如Data<void> :: f )。因此,Wrapper :: f 的有效生成特化是可能的,即Wrapper<void> :: f 。因此,总之,代码片段#1是良好的,代码片段#2是无效的;所有编译器都以符合标准的方式工作。


1
阅读此答案的编辑历史是一次真正的惊险之旅。 - Griwes
1
@Griwes 哈哈,这解释了很多事情!在它(暂时的?)最终形式中,这个答案值得点赞。 - YSC
@YSC 不用担心,我现在非常确定这没问题了。我必须承认,阅读标准让我参与了一个学习过程,显然我之前不确定答案可能是什么。 - Jodocus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接