标准的空参数类型,功能类似于 void
Standard empty argument type functioning like void
标准中是否有功能类似于void
的类型?也就是说,参数列表中实际上省略了一些东西,比如标签式结构?
void foo(){}
void foo(std::empty_type){}
// assembly for these two should be the same, no arguments
我的用例:我正在编写自己的基于范围的 for
迭代器对象。问题是返回的对象足以检查它是否在最后(我包装了一个 API)。这种基于范围的 for
受到了很多打击,任何微小的优化都会大大提高代码性能。问题是,我不能根据标准声明一元 operator !=
也不能声明类型 void
的符号,所以我想用大小为 0.[=18 的对象来模拟它=]
我可以使用什么类型来提示编译器完全忽略参数? std::monostate
之类的东西会起作用吗?根据我在 MSVC 上的测试,using empty_type = struct {}
不是解决方案。
The issue is that the object returned is sufficient for checking if it's at the end (I am wrapping an API).
C++20 将此识别为一个 "Iterator/Sentinel" 范围,它取代了传统的 "Iterator/Iterator" 模型,该模型要求两个迭代器是同一类型。 C++17 实际上是先发制人地扩展以支持这样的构造,因为 C++14 和之前要求基于范围的 for
循环范围的 begin
和 end
迭代器类型是同一类型。 C++17 对此进行了更改以允许不同的类型。
Sentinel 是一种类似于结束迭代器而不是迭代器的类型。事实上,the only real requirement of a Sentinel 是它可以与与之配对的 Iterator 进行相等性测试。任何测试等于其标记的迭代器都被认为是范围的末尾。
显然另一个 Iterator 是 Sentinel。但是 Sentinel 不必是 Iterator 的事实是强大的。
如果您有一个不是迭代器的 Sentinel 值,则 Sentinel 不执行任何操作的可能性很大。在大多数此类情况下,迭代器恰好存储了确定它是否在末尾所需的所有信息。 istream_iterator
和类似的基于流的迭代器就是这样的例子,它们可以有 Sentinels(它们没有,因为它们早于 Sentinel 范式。相反,这样一个范围的 "end" 迭代器是默认的-构造的迭代器)。所以你的 operator==
只是一种调用 Iterator.IsEnd()
或任何需要的功能的方法。
What type can I use to hint to the compiler to omit the argument altogether?
None。不过没关系,因为你不需要。
让我们看看我们的 Sentinel 值将存在于何处。在大多数情况下,Sentinel 只是触发 operator==
调用的一种方式,Sentinel 是单态的。这是一个独特的类型(重要,因为我们不希望 istream Sentinel 类型能够与计数范围或其他一些废话一起使用),但如果它有一个成员函数,它只是 operator==
.
这意味着范围类型不需要存储哨兵;它只需要 end
产生一个。所以它 returns 新创建的 Sentinel 的纯右值。
由于C++中的(完整)对象需要占用存储空间,所以它的大小在1到4字节之间。最坏的情况是,堆栈上有一个 4 字节的对象。
接下来,range-for
将在 Iterator/Sentinel 对上调用 operator==
。好吧,基本上没有理由不创建这个函数 inline
,因为这个函数所做的只是在迭代器上调用一个函数,所以编译器(优化)没有理由不内联函数。
一旦内联...编译器可以看到 Sentinel 没有做任何事情。由于该函数是内联的,因此不会作为参数传递。而且它实际上并没有被内联代码使用;它只是坐在那里。
最坏的情况是,编译器会在堆栈上留下一个 4 字节的对象,它什么都不做。但更有可能的是,编译器会删除它。
但如果您不想相信我的话,我会向您展示 nul_terminator
,一个指向以 NUL 结尾的字符串的 const char*
迭代器的 Sentinel。自然地,operator==
取消引用迭代器并针对 0.
对其进行测试
struct nul_terminator {};
//This code is C++17, since C++20 support is spotty, but you only need one of these in C++20.
bool operator==(const char *it, nul_terminator) {return *it == 0;}
bool operator==(nul_terminator, const char *it) {return *it == 0;}
bool operator!=(const char *it, nul_terminator) {return *it != 0;}
bool operator!=(nul_terminator, const char *it) {return *it != 0;}
class string_range
{
public:
const char *begin() {return str_;}
nul_terminator end() {return {};}
string_range(const char *str) : str_(str) {}
private:
const char *str_;
};
int manual_sum_values_of_string(const char *str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(; *str != 0; ++str)
accum += *str;
return accum;
}
int range_sum_values_of_string(string_range str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(auto ch : str)
accum += ch;
return accum;
}
如果您 look at the assembly output from the three major compilers 表示 manual_sum_values_of_string
与 range_sum_values_of_string
,您会发现它们实际上是相同的。我不是说 "there's overhead, but you can ignore it"。我是说 "the same, except the order of a couple of non-dependent opcodes is swapped."
GCC 在优化级别 1 下产生相同的结果,而 Clang 和 MSVC 需要 O2 才能获得相同的结果。
基本上:不用担心。编译器很聪明,所以让他们做他们的工作吧。
这就是哨兵的用途。
我假设您的迭代器看起来像这样:
struct iterator {
// ... usual iterator interface ...
// iterator knows when it's done
bool is_done() const;
};
您可以创建自己的哨兵类型:
struct is_done_sentinel { };
添加它们之间的比较(在C++20中只是一个运算符,在C++17中你必须写全部四个):
bool operator==(iterator const& lhs, is_done_sentinel ) {
return lhs.is_done(); // adjust as appropriate for your actual iterator
}
然后以你的范围return这个哨兵作为结束:
struct range {
iterator begin();
is_done_sentinel end() { return {}; }
};
这将具有您想要的行为:当迭代器完成时您的范围结束,并且检查没有开销。
标准中是否有功能类似于void
的类型?也就是说,参数列表中实际上省略了一些东西,比如标签式结构?
void foo(){}
void foo(std::empty_type){}
// assembly for these two should be the same, no arguments
我的用例:我正在编写自己的基于范围的 for
迭代器对象。问题是返回的对象足以检查它是否在最后(我包装了一个 API)。这种基于范围的 for
受到了很多打击,任何微小的优化都会大大提高代码性能。问题是,我不能根据标准声明一元 operator !=
也不能声明类型 void
的符号,所以我想用大小为 0.[=18 的对象来模拟它=]
我可以使用什么类型来提示编译器完全忽略参数? std::monostate
之类的东西会起作用吗?根据我在 MSVC 上的测试,using empty_type = struct {}
不是解决方案。
The issue is that the object returned is sufficient for checking if it's at the end (I am wrapping an API).
C++20 将此识别为一个 "Iterator/Sentinel" 范围,它取代了传统的 "Iterator/Iterator" 模型,该模型要求两个迭代器是同一类型。 C++17 实际上是先发制人地扩展以支持这样的构造,因为 C++14 和之前要求基于范围的 for
循环范围的 begin
和 end
迭代器类型是同一类型。 C++17 对此进行了更改以允许不同的类型。
Sentinel 是一种类似于结束迭代器而不是迭代器的类型。事实上,the only real requirement of a Sentinel 是它可以与与之配对的 Iterator 进行相等性测试。任何测试等于其标记的迭代器都被认为是范围的末尾。
显然另一个 Iterator 是 Sentinel。但是 Sentinel 不必是 Iterator 的事实是强大的。
如果您有一个不是迭代器的 Sentinel 值,则 Sentinel 不执行任何操作的可能性很大。在大多数此类情况下,迭代器恰好存储了确定它是否在末尾所需的所有信息。 istream_iterator
和类似的基于流的迭代器就是这样的例子,它们可以有 Sentinels(它们没有,因为它们早于 Sentinel 范式。相反,这样一个范围的 "end" 迭代器是默认的-构造的迭代器)。所以你的 operator==
只是一种调用 Iterator.IsEnd()
或任何需要的功能的方法。
What type can I use to hint to the compiler to omit the argument altogether?
None。不过没关系,因为你不需要。
让我们看看我们的 Sentinel 值将存在于何处。在大多数情况下,Sentinel 只是触发 operator==
调用的一种方式,Sentinel 是单态的。这是一个独特的类型(重要,因为我们不希望 istream Sentinel 类型能够与计数范围或其他一些废话一起使用),但如果它有一个成员函数,它只是 operator==
.
这意味着范围类型不需要存储哨兵;它只需要 end
产生一个。所以它 returns 新创建的 Sentinel 的纯右值。
由于C++中的(完整)对象需要占用存储空间,所以它的大小在1到4字节之间。最坏的情况是,堆栈上有一个 4 字节的对象。
接下来,range-for
将在 Iterator/Sentinel 对上调用 operator==
。好吧,基本上没有理由不创建这个函数 inline
,因为这个函数所做的只是在迭代器上调用一个函数,所以编译器(优化)没有理由不内联函数。
一旦内联...编译器可以看到 Sentinel 没有做任何事情。由于该函数是内联的,因此不会作为参数传递。而且它实际上并没有被内联代码使用;它只是坐在那里。
最坏的情况是,编译器会在堆栈上留下一个 4 字节的对象,它什么都不做。但更有可能的是,编译器会删除它。
但如果您不想相信我的话,我会向您展示 nul_terminator
,一个指向以 NUL 结尾的字符串的 const char*
迭代器的 Sentinel。自然地,operator==
取消引用迭代器并针对 0.
struct nul_terminator {};
//This code is C++17, since C++20 support is spotty, but you only need one of these in C++20.
bool operator==(const char *it, nul_terminator) {return *it == 0;}
bool operator==(nul_terminator, const char *it) {return *it == 0;}
bool operator!=(const char *it, nul_terminator) {return *it != 0;}
bool operator!=(nul_terminator, const char *it) {return *it != 0;}
class string_range
{
public:
const char *begin() {return str_;}
nul_terminator end() {return {};}
string_range(const char *str) : str_(str) {}
private:
const char *str_;
};
int manual_sum_values_of_string(const char *str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(; *str != 0; ++str)
accum += *str;
return accum;
}
int range_sum_values_of_string(string_range str)
{
int accum = 0; //Just to give the function something to do, so the optimizer doesn't make it go away.
for(auto ch : str)
accum += ch;
return accum;
}
如果您 look at the assembly output from the three major compilers 表示 manual_sum_values_of_string
与 range_sum_values_of_string
,您会发现它们实际上是相同的。我不是说 "there's overhead, but you can ignore it"。我是说 "the same, except the order of a couple of non-dependent opcodes is swapped."
GCC 在优化级别 1 下产生相同的结果,而 Clang 和 MSVC 需要 O2 才能获得相同的结果。
基本上:不用担心。编译器很聪明,所以让他们做他们的工作吧。
这就是哨兵的用途。
我假设您的迭代器看起来像这样:
struct iterator {
// ... usual iterator interface ...
// iterator knows when it's done
bool is_done() const;
};
您可以创建自己的哨兵类型:
struct is_done_sentinel { };
添加它们之间的比较(在C++20中只是一个运算符,在C++17中你必须写全部四个):
bool operator==(iterator const& lhs, is_done_sentinel ) {
return lhs.is_done(); // adjust as appropriate for your actual iterator
}
然后以你的范围return这个哨兵作为结束:
struct range {
iterator begin();
is_done_sentinel end() { return {}; }
};
这将具有您想要的行为:当迭代器完成时您的范围结束,并且检查没有开销。