神秘的 C++ 可变参数模板扩展

Mysterious C++ variadic template expansion

以下 C++ 函数摘自第 151 - 157 行 here:

template <typename... T>
std::string JoinPaths(T const&... paths) {
  boost::filesystem::path result;
  int unpack[]{0, (result = result / boost::filesystem::path(paths), 0)...};
  static_cast<void>(unpack);
  return result.string();
}

函数 JoinPaths("foo", "bar", "doo.txt") 将 return 一个 std::string"foo/bar/doo.txt" (我认为)所以我理解函数的语义。

我试图理解 return 语句之前的两行。 unpackint 的数组中,但是前导 0 和末尾的省略号发生了什么(以及为什么)。有人可以解释这是如何扩展的吗?为什么是0?我假设 static_cast 是为了防止编译器优化数组?

正如我在输入时已经指出的那样,这是一种执行 fold expression.

的 C++17 之前的方法

它使用本地临时 int 数组和逗号运算符来隐藏一堆原本应该是的内容:

result = result / paths[n]

转换为整数表达式,最终生成:

int unpack[]{
  0, 
  (result = result / paths[0], 0),
  (result = result / paths[1], 0),
  ...,
  (result = result / paths[N], 0)
};

这些天,将执行以下操作:

template <typename...Pathnames>
std::string JoinPaths( Pathnames...pathnames )
{
  return (std::filesystem::path( pathnames ) / ...).string();
}
int unpack[]{0, (result = result / boost::filesystem::path(paths), 0)...};

第一个 0 是为了在有人用零参数调用函数时不尝试创建空数组。

(result = result / boost::filesystem::path(paths), 0)

这会评估 result = result / boost::filesystem::path(paths) 并丢弃它。结果是逗号右边的0

... 是一个 包扩展 ,在每个包之间放置一个 ,,使其成为:

int unpack[]{0, (result = result / boost::filesystem::path("foo"),0), 
                (result = result / boost::filesystem::path("bar"),0),
                (result = result / boost::filesystem::path("doo.txt"),0)
            };

因此,unpack 中将有四个 0:s。之后的转换只是为了禁用关于未使用变量的警告 unpack.

自 C++17 起:

[[maybe_unused]] int unpack ... 可以用来代替删除警告。也可以使用 fold expressionconstexpr if 来完全跳过 unpack 变量——也可以使用 std::filesystem而不是 boost::filesystem:

template<class... Ps>
std::string JoinPaths2(Ps&&... paths) {
    std::filesystem::path result;
    if constexpr (sizeof...(Ps)) // can't unfold empty expansion over /
        result = (std::filesystem::path(std::forward<Ps>(paths)) / ...);
    return result.string();
}