On customization points and niebloids
#44
Replies: 6 comments
-
The current approach to static dispatch in |
Beta Was this translation helpful? Give feedback.
-
P2547 has a very interesting summary of why ranges CPOs are not a panacea:
|
Beta Was this translation helpful? Give feedback.
-
Hmm, I don't follow why the compiler wouldn't be able to see right through. |
Beta Was this translation helpful? Give feedback.
-
Copy elision only works with pass-by-value semantics. If we're taking a reference, objects cannot be directly constructed into the storage where they would be copied/moved to. Even if we try to forward the reference by casting it to rvalue reference, that becomes an xvalue, not a prvalue. Just to double check, I wrote this example (godbolt link): Compiler flags: #include <string>
#include <iostream>
namespace _foo {
void foo();
struct _fn {
template<typename T, typename V>
requires requires (T& obj, V&& value) {
foo(obj, (V&&)value);
}
void operator()(T& obj, V&& value) const {
foo(obj, (V&&)value);
}
};
}
inline namespace _foo_cpo {
inline constexpr _foo::_fn foo{};
}
struct string_wrapper {
string_wrapper(std::string str_) : str{str_} {
std::cout << "constructing\n";
}
string_wrapper(const string_wrapper& other) : str{other.str} {
std::cout << "copying!\n";
}
string_wrapper(string_wrapper&& other) : str{std::move(other.str)} {
std::cout << "moving!\n";
}
std::string str;
};
struct my_type {
friend void foo(my_type& self, string_wrapper value) {
std::cout << value.str;
};
friend void bar(my_type& self, string_wrapper value) {
std::cout << value.str;
};
};
int main() {
my_type t;
foo(t, string_wrapper(std::string{"hello\n"})); // will result in an extra call to
// string_wrapper's move constructor.
bar(t, string_wrapper(std::string{"bye\n"}));
} Output:
|
Beta Was this translation helpful? Give feedback.
-
Ah, I was thinking of RVO as applied to the return value of the CPO in an eligible scenario, not to temporaries used for argument passing. Yes, it's pass-by-value semantics or nothing. |
Beta Was this translation helpful? Give feedback.
-
Closed by #272 |
Beta Was this translation helpful? Give feedback.
-
Customization points and ADL
Customization points are hooks used by generic code that end-users can specialize to customize the behavior for their types. The STL provides plenty of examples:
begin
,end
,swap
, etc.Argument-dependent lookup is a set of rules for looking up the unqualified function names in function-call expressions, including implicit function calls to overloaded operators and is very relevant when implementing customization points.
If one were to make a call to a customization point function in generic code, the correct way to do it is the following:
This is known as the std-swap-two-step idiom and is used within the STL itself (see
Swappable
). The alternatives are incorrect (in a generic context) for the following reasons:It's really a shame that the correct way to do this is not intuitive and, when a library author provides customization points based on pre-C++20 standard patterns, it's the user's responsibility to do the right thing.
Niebloids!
Eric Niebler proposed a solution for this when designing the
range-v3
library (and in 2018, he also suggested a name for it 😅).Now, instead of having a function, we have a global functor (called customization point object or CPO) that has the following properties:
The responsibility of doing the right thing is back in the hands of the library author, making the API safer. And as a bonus, they work really well with concept checking.
From C++20 onwards, we have CPOs in the standard library, i.e. see
std::ranges::swap
.But wait, there is more...
Beta Was this translation helpful? Give feedback.
All reactions