如何在匿名命名空间中调用可变参数模板助手?

How to call a variadic template helper in anonymous namespace?

在下面的代码中,foo 应该是任何人都可以访问的函数,但 foo_helper 不应该,这就是我将其放在匿名命名空间中的原因。显然我在这个例子中遗漏了包括警卫和包括,但他们在那里。

foo.h:

namespace
{
    void foo_helper() {}

    template <typename T, typename... Tail>
    void foo_helper(T head, Tail... tail)
    {
        bar(head);
        foo_helper(tail...);
    }
}

void foo();

template <typename... Args>
void foo(Args... args)
{
    before();
    foo_helper(args...);
    after();
}

foo.cpp:

void foo() {}

问题是为了让 foo_helper 的可变参数模板工作,它需要有没有参数的初始版本。但是,这迫使我定义一个非模板函数是一个头文件,在将这个文件包含在多个源文件中后它会中断。我无法将 foo_helper 的定义移动到源文件中,因为它位于匿名命名空间中,因为它不应该是可访问的。

有办法解决这个问题吗?

inline void foo_helper() {};

解决了您的问题。

inline 主要是指 "conflicting definitions of this function are to be discarded, and one of the versions kept".

它还 non-bindingly 以模糊的方式建议 "inlining"(因为该标准并未真正涵盖内联是什么)。编译器可能会或可能不会注意该建议。

请注意,匿名命名空间不会 "make it unusable" 或其他任何东西。匿名命名空间旨在阻止 linker 冲突,仅此而已。创建一个名为 details 的命名空间,然后......好吧,相信用户不要去往里面戳。

在 header 中使用匿名命名空间是一个非常糟糕的主意

如果另一个 header 文件中有一个 inline 函数(或模板函数)访问匿名命名空间中的符号或函数,您几乎肯定会有一个 ODR(一个定义规则)违规。这就是相同的 object、函数等有两个不同的定义,这是不允许的。

例如:

inline void bob() {
  foo(1,2,3);
}

如果那是 #included 在两个不同的 .cpp 文件中,您刚刚制作了一个 ill-formed 程序(不需要诊断)。

这种格式错误的程序通常"behave the way you expect",但有时它们不会。例如,如果沿线某处你得到一个 static 局部变量,其存在取决于 ODR 违规,你可以让多个编译单元不同意哪个存在以及它的属性是什么。

从更一般的意义上讲,程序的 link 顺序可能会改变其行为,因为不同的定义是 "chosen"(可能存在极其细微的差异)。或者月相也可以做同样的事情。

ODR 违规出人意料地良性,直到它们用 non-local 难以追踪的错误来攻击你。

我将从旁白开始:在这里使用匿名名称空间不符合您的目的。由于您在头文件中定义它,因此它根本不受保护:它仍然在包含您的头文件的任何文件的范围内。此外,由于您已在匿名命名空间中定义它,因此每个使用它的翻译单元都会发出该函数的单独副本,并且链接器无法折叠它们。如果您真的希望它是私有的,那么这些天我并不是最好的 C++ 风格的升级者,所以也许其他人会纠正我,但我倾向于使用私有命名空间:

namespace my_stuff {
    void foo_helper();
}

void foo() {
    my_stuff::foo_helper();
}

正如 Yakk 所指出的,您可以使用内联函数,这将允许编译器将定义折叠为一个。在现代实践中,实际上不应该有另一个理由来避免使用 inline 关键字,因为现在编译器将自行决定是否内联函数,而不是听取您提供的提示。

由于您已经在匿名命名空间中定义了函数,正如我上面提到的,如果您保留它,您实际上不需要做任何其他事情来避免链接器错误。这种方法的缺点是您将在每个翻译单元中拥有单独的 foo_helper() 副本,并且链接器无法合并这些副本。

您还可以做其他体操,主要涉及 sizeof...,但我认为这些都不理想。