tag_invoke
Argument Dependent Lookup
#include <iostream>
namespace lib1 {
template <class T>
void print(T const& t) {
std::cout << "lib1::print()" << std::endl;
}
} // namespace lib1
namespace lib2 {
struct w {
int x_;
};
void print(w const&) {
std::cout << "lib2::print()" << std::endl;
}
} // namespace lib2
lib2::w x;
lib1::print(x); // #1 lib1::print -- NOT EXPECTED
lib2::print(x); // #2 lib2::print
using namespace lib1;
print(x); // #3 lib2::print -- ADL
当我们在基础库 lib1
中定义 print
方法后,希望用户可以根据自己的需要自定义 print
的行为。如上述代码,期望以限定名 lib1::print(x)
调用时可以调用用户自定义的代码,然而并没有达到期望。
Customization Point Object
#include <iostream>
namespace lib1 {
namespace cpo {
struct print_t {
template <class T>
friend void print(T const& t) {
std::cout << "lib1::print()" << std::endl;
}
template <class T>
constexpr void operator()(T&& t) const {
return print(t);
}
};
} // namespace cpo
constexpr cpo::print_t print;
} // namespace lib1
// ... lib2 code ...
lib2::w x;
lib1::print(x); // #1 lib2::print -- EXPECTED
lib2::print(x); // #2 lib2::print
using namespace lib1;
print(x); // #3 lib2::print -- EXPECTED
为了解决以限定名 lib1::print(x)
调用时,可以调用用户自定义的代码,Eric Niebler 在他的 range-v3 中提出了 CPO 的想法。当把函数提升为对象后可以避免参数依赖查找,然后在对象内正确处理需要调用的函数,如上述代码。
后来,Eric Niebler 在实践 libunifex 过程中,发现太多需要自定义的地方,导致用户在用户的命名空间内大量声明 libunifex 要求的函数,污染了用户命名空间,于是对 CPO 进行改进,提出了 tag_invoke
。
tag_invoke
#include <iostream>
#include <type_traits>
namespace lib1 {
constexpr struct tag_invoke_t {
template <class CPO, class T>
constexpr void operator()(CPO&& cpo, T&& t) const {
return tag_invoke(std::forward<CPO>(cpo), std::forward<T>(t));
}
} tag_invoke;
namespace cpo {
struct print_t {
template <class T>
friend void tag_invoke(print_t, T const& t) {
std::cout << "lib1::print()" << std::endl;
}
template <class T>
constexpr void operator()(T&& t) const {
return lib1::tag_invoke(print_t{}, std::forward<T>(t));
}
};
} // namespace cpo
constexpr cpo::print_t print;
} // namespace lib1
namespace lib2 {
// ... struct w ...
void tag_invoke(std::remove_cvref_t<decltype(lib1::print)>, w const&) {
std::cout << "lib2::print()" << std::endl;
}
} // namespace lib2
lib2::w x;
lib1::print(x); // #1 lib2::print -- EXPECTED
using namespace lib1;
print(x); // #2 lib2::print -- EXPECTED
tag_invoke
本质上仍为 CPO,然后用这个 CPO 通过将需要自定义的 CPO 提升为函数参数来统一自定义函数的名称。
Customisable Function Prototype
CFP 提出了语法级别上支持自定义函数的方案,目前还不支持,详见 P2547R0。