为什么我的(C++)编译器在使用std::endl时要实例化我的参数包类?

19

考虑以下简短的程序。

#include <iostream>

template< typename ... Ts >
class foobar
{
    static_assert( sizeof...(Ts) > 0 );
};

template< typename ... Ts >
std::ostream& operator<<( std::ostream& o, const foobar< Ts... >& )
{
    return o;
}

int main(void)
{
    std::cout << "This has nothing to do with foobar" << std::endl;
}

当我们试图编译这个时,我们会得到...

ted@tedscomputer:~/Projects/emptypack$ g++ -o emptypack main.cpp
main.cpp: In instantiation of ‘class foobar<>’:
main.cpp:17:63:   required from here
main.cpp:6:34: error: static assertion failed
    6 |     static_assert( sizeof...(Ts) > 0 );
      |                    ~~~~~~~~~~~~~~^~~
ted@tedscomputer:~/Projects/emptypack$

我知道std::endl有点奇怪(以至于它实际上拥有自己的StackOverflow标签),但是这里到底出了什么问题?为什么编译器尝试使用空类型参数来实例化我的完全无关的类,失败,并生成一个错误?

更重要的是,每次我编写一个带参数包的类,我是否都需要确保它可以不带任何类型参数进行实例化,即使这对于程序逻辑来说毫无意义,只是为了防止它接近std::endl


4
对于任何想知道的人,这种情况在GCC、Clang和MSVC上都会发生,所有三种编译器都提供类似无用的诊断信息。 - HolyBlackCat
2
将条件移动到“requires”中可以解决问题。 - HolyBlackCat
4
是的,因为这会使得推断出Ts为空参数包(https://dev59.com/gVMI5IYBdhLWcg3wDXHi)成为一种替换失败,而不是在实例化类时硬性失败(我认为是为了查找转换构造函数?)。 - Artyer
1个回答

7

@Artyer 发现了这个:

[temp.arg.explicit]/4

...[注1: 如果尾随的模板参数包没有被推导出来,则会被推导为空模板参数序列。— 结束注释]

通常,如果你只是将特定类型的对象传递给第二个参数,条件是不满足的:

::operator<<(std::cout, foobar<int>{}); // compiles

即使你传递了一个不同类型的对象,条件仍然不满足:
// Causes `error: no matching function for call to 'operator<<'`,
// does NOT trigger the `static_assert`.
::operator<<(std::cout, 0);

我猜测这是因为编译器试图从参数类型中推断出Ts...,但失败了。
如果我们将参数拼写为std::enable_if_t<true, const foobar<Ts...> &>,则条件总是满足的,因为无法从中推断出Ts...,编译器甚至不尝试。
但是为什么我们的情况下条件得到满足呢?因为std::endl是一个函数模板(未指定模板参数),没有类型,因此无法从中推断任何内容。

有些高调的人嘲笑我使用 endl,就像这样:endl(std::cout<<"无论接下来发生什么"); 这看起来相当表达清晰、简单,并且不容易出现故障。 - Red.Wave

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