自定义点和 ADL

Customization points and ADL

我正在编写一个库,并且有一个函数使用任意类型作为参数执行对自由函数 foo 的(非限定)调用:

namespace lib {

template <typename T>
auto libfunc(T && t)
{
  return foo(std::forward<T>(t));
}

} // namespace lib

库的用户可以为自己的类型编写 foo 的重载:

namespace app {

class Foo { };

template <typename> class Bar { };

int foo(Foo const &) { return 99; }

template <typename T>
char foo(Bar<T> const &) { return 'x'; }

} // namespace app

ADL 找到了正确的函数 foo,因此这样的代码有效:

app::Foo foo;
app::Bar<void**> bar;

auto x = lib::libfunc(foo);
auto y = lib::libfunc(bar);

但是,如果我想编写适用于 std 命名空间类型的 foo 版本,则找不到匹配的函数 foo,除非我将 foostd- 不允许的名称空间中:

#ifdef EVIL
namespace std {
#endif

template <typename T>
double foo(std::vector<T> const & ) { return 1.23; }

#ifdef EVIL
} // namespace std
#endif

std::vector<int> vec;

lib::libfunc(vec); // Only works if EVIL is defined

是否可以更改代码,以便用户可以在不侵入其名称空间的情况下为类型启用功能 foo?我考虑过 lib 命名空间中 class 模板的部分模板特化,但还有其他可能性吗?

我找到了解决这个问题的两个方法。两者都有缺点。

声明所有标准重载

让标准类型的重载通过正常查找找到。这基本上意味着在使用扩展功能之前声明所有这些。请记住:当您在函数模板中执行非限定调用时,正常查找发生在定义点,而 ADL 发生在实例化点。这意味着普通查找只会找到从模板编写位置可见的重载,而 ADL 会找到稍后定义的内容。

这种方法的好处是用户在编写自己的函数时没有任何变化。

缺点是您必须包含要为其提供重载的每个标准类型的 header,并在只想定义扩展的 header 中提供该重载观点。这可能意味着非常严重的依赖性。

添加另一个参数

另一种选择是将第二个参数传递给函数。这是它的工作原理:

namespace your_stuff {
    namespace adl {
        struct tag {}

        void extension_point() = delete; // this is just a dummy
    }

    template <typename T>
    void use_extension_point(const T& t) {
        using adl::extension_point;
        extension_point(t, adl::tag{}); // this is the ADL call
    }

    template <typename T>
    void needs_extension_point(const T& t) {
        your_stuff::use_extension_point(t); // suppress ADL
    }
}

现在您基本上可以在程序的任何位置为 std(甚至全局或 built-in)类型提供重载,如下所示:

namespace your_stuff { namespace adl {
    void extension_point(const std::string& s, tag) {
        // do stuff here
    }
    void extension_point(int i, tag) {
        // do stuff here
    }
}}

用户可以为自己的类型编写这样的重载:

namespace user_stuff {
    void extension_point(const user_type& u, your_stuff::adl::tag) {
        // do stuff here
    }
}

上行:有效。

缺点:用户必须将 your_stuff::adl::tag 参数添加到他的重载中。这可能会被许多人视为烦人的样板,更重要的是,当用户忘记添加参数时,会导致令人费解的 "why doesn't it find my overload" 大问题。另一方面,该参数还清楚地将重载标识为履行合同(作为扩展点),这在下一位程序员出现并将函数重命名为 extensionPoint(以符合命名约定)时可能很重要然后当事情不再编译时吓坏了。