自定义点和 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
,除非我将 foo
在 std
- 不允许的名称空间中:
#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
(以符合命名约定)时可能很重要然后当事情不再编译时吓坏了。
我正在编写一个库,并且有一个函数使用任意类型作为参数执行对自由函数 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
,除非我将 foo
在 std
- 不允许的名称空间中:
#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
(以符合命名约定)时可能很重要然后当事情不再编译时吓坏了。