我最近了解了自定义点对象模式并尝试实现它。起初,它看起来像是一种很好的方法来实现一些基本功能并将其扩展到不同类型。
在下面的示例中,我使用启用了 c++20 的 Visual Studio 2022。
我最终得到了这段代码,与此类似。我使用 std::ranges::swap 作为参考。
namespace feature {
namespace _feature_impl {
/* to eliminate lookup for wrong functions */
template<typename T>
void use_feature(T&) = delete;
/* filtering customizations */
template<typename T>
concept customization =
requires(T& reward) {
{ use_feature(reward) };
};
struct fn {
/* allow only if there is customization */
/* compile-time error otherwise */
constexpr void operator () (customization auto& reward) const {
use_feature(reward);
}
};
}
/* main interface to access feature */
inline constexpr auto apply = _feature_impl::fn{};
}
按预期工作时的使用示例.
/* result: compiles, prints "Foo" */
struct Foo {};
void use_feature(Foo&) {
std::cout << "Foo\n";
}
auto main(int argc, char const** argv) -> int {
auto foo = Foo{};
feature::apply(foo);
return 0;
}
/* result: doesn't compile */
struct Foo {};
struct Bar {};
void use_feature(Foo&) {
std::cout << "Foo\n";
}
auto main(int argc, char const** argv) -> int {
auto bar = Bar{};
/* passing an object of type, that is not supported */
feature::apply(bar);
return 0;
}
/* result: compiles, prints "Foo" */
namespace bar {
struct Foo {};
void use_feature(Foo&) {
std::cout << "Foo\n";
}
void main() {
auto foo = Foo{};
feature::apply(foo);
}
}
auto main(int argc, char const** argv) -> int {
bar::main();
return 0;
}
bar::main
放入bar::baz::main
/* result: compiles, prints "Foo" */
namespace bar {
struct Foo {};
void use_feature(Foo&) {
std::cout << "Foo\n";
}
namespace baz {
/* now usage in nested namespace baz */
void main() {
auto foo = Foo{};
feature::apply(foo);
}
}
}
auto main(int argc, char const** argv) -> int {
bar::baz::main();
return 0;
}
但是有些例子不太明白,为什么它们不起作用.
feature::apply
访问时,报错/* result: doesn't compiles */
/* Error C3889 - call to object of class type 'feature::_feature_impl::fn': no matching call operator found */
struct Foo {};
namespace bar {
void use_feature(Foo&) {
std::cout << "Foo\n";
}
void main() {
auto foo = Foo{};
feature::apply(foo);
}
}
auto main(int argc, char const** argv) -> int {
bar::main();
return 0;
}
/* result: doesn't compiles */
/* Error C3889 - call to object of class type 'feature::_feature_impl::fn': no matching call operator found */
void use_feature(int&) {
std::cout << "Foo\n";
}
auto main(int argc, char const** argv) -> int {
auto i = int{0};
feature::apply(i);
return 0;
}
可能是我在第一个不工作的示例中遗漏了一些范围解析规则,但即使这样也不能解释为什么它不适用于具有任何命名空间组合的内置类型。 std::ranges::swap 做同样的事情。这意味着,例如,如果我需要为某种类型添加自定义,我需要将它放在定义此类的同一命名空间中。
假设,在标准库中没有
swap(std::string&, std::string&)
,或者我出于某种原因需要替换它,我应该做这样的事情。
namespace std {
void swap(std::string&, std::string&) {
std::cout << "Foo\n";
}
}
auto main(int argc, char const** argv) -> int {
auto s = std::string{};
std::ranges::swap(s, s);
return 0;
}
我觉得不对。
最初我认为函数
use_feature
的查找会延迟到feature::apply
调用,因为feature::apply::operator()
是一个函数模板,并且在这个函数中调用use_feature
使用模板参数。它看起来像是一种简单灵活的方式来扩展不同类型的功能。但是比起我实现它,尝试在不同的命名空间中移动部分并尝试使用不同的类型......
在我看来,自定义函数
use_feature
将在当前名称空间或更高名称空间中查找是合乎逻辑的。
第一个非编译示例失败,因为
use_feature()
是要由ADL 找到的。然而,这要求函数在与其参数相同的命名空间中声明。您的 use_feature(Foo&)
是在 nested 命名空间 bar
中声明的,因此 ADL 不考虑它。
第二个例子失败,因为ADL不适用于基本类型,所以在
fn::operator()
中通过重载决策找到的函数是删除的use_feature
函数模板。
您可以通过在命名空间
use_feature(int&)
中声明
_feature_impl
来解决这个问题