定制点和ADL

时间:2015-01-21 15:12:43

标签: c++ argument-dependent-lookup

我正在编写一个库,并且有一个函数使用任意类型作为参数对自由函数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);

但是,如果我想编写适用于foo - 命名空间类型的std版本,除非我放置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 - 命名空间中类模板的部分模板特化,但还有其他可能吗?

1 个答案:

答案 0 :(得分:3)

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

声明所有标准重载

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

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

缺点是你必须在想要定义扩展点的标题中包含你想要提供重载的每个标准类型的标题,并提供该重载。这可能意味着非常沉重的依赖。

添加另一个参数

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

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(甚至全局或内置)类型提供重载,如下所示:

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参数添加到他的重载中。这可能被很多人看作是令人烦恼的样板,更重要的是,当用户忘记添加参数时,可能会导致“为什么不找到我的超载”这个问题。另一方面,该参数还清楚地将重载标识为履行合同(作为扩展点),当下一个程序员出现并将函数重命名为extensionPoint(以符合命名约定)时,这可能很重要。然后当事情不再编译时就吓坏了。