I recently learned about customization point object pattern and tried implementing it. At first, it looked like a very good way to make some base functionality and extend it on different types.
In examples below I use Visual Studio 2022 with c++20 enabled.
I ended up with this code, similar to this. I used std::ranges::swap as a reference.
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{};
}
Examples of usage when it works as expected.
- Struct and customization function defined in global namespace
/* 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;
}
- Same as before, but customization is not defined
/* 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;
}
- Same as the first example, but struct, customization function and usage of the feature are inside of namespace bar
/* 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;
}
- Putting
bar::main
inbar::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;
}
But there are some examples that a can't quite understand, why they don't work.
- Struct and customization defined in different namespace. Customization function can clearly see the definition, but when accessing though feature interface function
feature::apply
, I'm getting error.
/* 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;
}
- Defining customization function for built-in types like int.
/* 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;
}
May be I'm missing some scope resolution rules with the first not working example, but even that doesn't explain why it doesn't work with built-in types with any combinations of namespaces. std::ranges::swap does the same things. It means if, for example, I need to add customization for some type, I need to place it in the same namespace where this class is defined.
Assuming, that in standard library there is no swap(std::string&, std::string&)
or I, for some reason, need to replace it, I should do something like this.
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;
}
It doesn't feel right to me.
Initially I thought that lookup for function use_feature
will be delayed until feature::apply
call, because feature::apply::operator()
was a function template, and call to use_feature
inside this function used templated argument. It looked like an easy and flexible way to extent functionality on different types. But than I implemented it, tried to move around parts in different namespaces and tried to use with different types...
It seemed logical to me that customization functions use_feature
would be looked up in current namespace or higher.