(Disclaimer: here be esoteric language wonkery. Abandon all hope.)
If you read or write Generic-code-with-a-capitol-‘G’, you may have written or seen code like this:
using std::swap; swap( a, b );
The first line brings std::swap
into consideration, and the second makes an unqualified call to the swap
function. I call this the “Std Swap Two-Step”.
Why do we do the Two-Step? It has to do with the decidedly wonky way C++ resolves function calls in templates. I won’t fully elaborate on two-phase name lookup (you’re welcome), but suffice it to say that we do it for the sake of genericity: We make an unqualified call to swap
in order to find an overload that might be defined in a
and b
‘s associated namespaces (itself a rich topic), and we do using std::swap
so that, on the off-chance that there is no such overload, we find the default version defined in the std
namespace.
We call swap
and functions like it customization points — hooks used by generic code that end-users can specialize to customize the behavior for their types.
Are there other standard customization points? You bet. When you use a range-based for
loop, the compiler inserts calls to unqualified begin
and end
to get the bounds of the range. So begin
and end
are customization points. Depending on how you read the specification of std::reverse
, iter_swap
may also be a customization point. (I think it is, or that it should be.) And other customization points may be in the offing. Proposal N4155, proposes non-member size
for fetching the size of a range, and my own N4128 will propose size
as a customization point as well.
Trouble With The Two-Step
Have you seen code that makes qualified calls to swap
in a template, like std::swap( a, b );
? Congratulation, you have probably found a bug. If the type of a
and b
overloads swap
in its own namespace, a qualified call to std::swap
won’t find it. It’s an all-too-easy mistake to make.
The problem with the Two-Step is that is forces users to type more to do the right thing. FAIL. Most damning, it requires users to either blindly memorize and regurgitate the Two-Step pattern, or worse: understand two-phase name lookup in templates.
Through With The Two-Step
We need a better solution for the design of customization points in C++. In my own range library, I gave a great deal of thought to the problem, and I think I have an answer. Below is how I would like to see a future version of the Standard Library define std::begin
, to pick an example at random. I explain it after the break:
namespace std { namespace __detail { // define begin for arrays template<class T, size_t N> constexpr T* begin(T (&a)[N]) noexcept { return a; } // Define begin for containers // (trailing return type needed for SFINAE) template<class _RangeLike> constexpr auto begin(_RangeLike && rng) -> decltype(forward<_RangeLike>(rng).begin()) { return forward<_RangeLike>(rng).begin(); } struct __begin_fn { template<class R> constexpr auto operator()(R && rng) const -> decltype(begin(forward<R>(rng))) { return begin(forward<R>(rng)); } }; } // To avoid ODR violations: template<class T> struct __static_const { static constexpr T value{}; }; template<class T> constexpr T __static_const<T>::value; // std::begin is a global function object! namespace { constexpr auto const & begin = __static_const<__detail::__begin_fn>::value; } }
Let’s break this down. First, we define a couple of begin
free functions in a std::__detail
namespace. These overloads handle array types and range-like things with .begin()
member functions. (Think of the standard containers.)
Next, we define a __begin_fn
class with an overloaded function call operator in the std::__detail
namespace that returns the result of making an unqualified call to begin
. At this point in the source code, the name begin
refers to a function overload set.
Finally, we define a std::begin
object of type std::__detail::__begin_fn
in a round-about sort of way, the details of which are not too relevant. The important bit is that std::begin
is a function object.
Implementers of range-like types can hook this customization point the same way they always have: by defining a begin
free function in their type’s associated namespace. See below:
namespace NS { struct S {}; int * begin( S & s ); } int main() { NS::S s; int *p = std::begin(s); // calls NS::begin(s) }
Function Objects and Customization Points
Argument-dependent lookup and customization points are a match made in heaven. But argument-dependent lookup is only done for free functions, and my std::begin
is a function object. Argument-dependent lookup isn’t done for function objects. What’s going on?
The short answer is that the std::begin
function object is doing the Two-Step so that you don’t have to. If std::begin
were defined this way, you could just make qualified calls to std::begin
and the right thing would happen. You could also do the Two-Step, bringing std::begin
into scope with a using
declaration, and calling it unqualified, and get the same behavior. Either way, if there is a begin
free function defined in the argument’s associated namespace, it will get used.
A subtle but important point is that, if you do the Two-Step, the call is still routed through the std::begin
function object. I mean that in the code below:
using std::begin; begin( v );
…if std::begin
were an object instead of a function, then what looks like an unqualified function call is not; it’s a call to std::__detail::__begin_fn
‘s overloaded function call operator. Think of this as the Generic equivalent of the Gang of Four‘s Template method pattern:
In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in a method, called template method, which defers some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm’s structure. This use of “template” is unrelated to C++ templates.
— Wikipedia
In this case, the “algorithm” is std::begin
, and the certain steps that users can redefine is begin
. What’s the point, you ask? We can do extra parameter checking in std::begin
. Read on.
Customization Points and Concepts Lite
Customization points are scary in a way. In today’s language, if you define a free function called swap
, it better do what the Standard Library expects swap
to do. Otherwise, all hell breaks loose in the standard algorithms. Likewise, you can shoot yourself if you define a begin
or end
free function that doesn’t return iterators. So the Standard Library has laid claim to those names globally. That’s why customization points are such a concern for the standardization committee; the more we add, the more names we reserve globally, and the bigger the potential problem gets for the users.
Enter Concepts Lite. With Concepts Lite, we can constrain our customization points to only work with the types that model certain concepts. For instance, it should be an error to call std::begin
on something that doesn’t look like a range, don’tcha think? With Concepts Lite and global function objects, we can have that. We can define std::__detail::__begin_fn
like this:
// A _RangeLike is something we can call begin(r) // and end(r) on: concept _RangeLike<class T> = requires(T t) { typename IteratorType<T>; { begin(t) } -> IteratorType<T>; { end(t) } -> IteratorType<T>; requires Iterator<IteratorType<T>>; }; struct __begin_fn { // LOOK! R must be _RangeLike! template< _RangeLike R > constexpr auto operator()(R && rng) const -> decltype(begin(forward<R>(rng))) { return begin(forward<R>(rng)); } };
First we define the _RangeLike concept as something on which we can call begin
and end
, such that they both return iterators of the same type. (Or, if you agree with N4128, different types that are comparable.) Then we use the _RangeLike concept to constrain __begin_fn::operator()
and by extension std::begin
. Now std::begin
won’t compile for things that aren’t sufficiently range-like, which makes it safer to lay claim to a common identifier like begin
.
If std::begin
is a function object as opposed to a free function, it’s not easy to route around this concept checking. Code that does the Two-Step won’t accidentally hijack some unrelated begin
function in some random namespace. It will always resolve to std::begin
, which will politely reject invalid code.
You don’t have to wait for Concepts Lite to reap the benefits, either. See my post on emulating Concepts Lite in C++11.
Summary
What’s all this mean? Simply:
- Users could just call
std::begin
and it would do ADL for them. std::begin(rng)
wouldn’t compile unless:- it returns an iterator, and
std::end(rng)
also compiles and returns an iterator of the same type.
- Code that does
using std::begin; begin(rng);
isn’t going to dispatch to some randombegin
function unless the argument satisfies the constraints ofstd::begin
.
More generally, there is a design pattern that we can use to make safe and convenient customization points. If you’re writing a generic library with customization points, I recommend using this pattern.
Addendum: An Ode to Global Function Objects
We get an additional benefit from making std::begin
a global function object:
- You can pass
std::begin
as an argument to higher-order functions.
That’s a benefit of function objects over free functions in general, and it’s why I generally prefer global function objects over free functions these days (except when I’m defining customization points). Defining global function objects is more work, but it has the nice effect of turning off argument-dependent lookup, which really only makes sense for operator overloads and customization points. First-order functions rule. ADL sucks (except in the few places where it’s awesome).
Update
A quick note about generic lambdas, since I’ve gotten questions. In C++14, we can define polymorphic function objects very concisely using generic lambdas. So can we use lambdas to define global function objects and save some typing, as below:
// Better? constexpr auto begin = [](auto && rng) { using __detail::begin; return begin(forward<decltype(rng)>(rng)); };
The answer, sadly, is no for a host of reasons:
- Lambdas do not have
constexpr
constructors. - I don’t know how to solve the ODR problem for lambdas. If
std::begin
were defined this way, then each translation unit would see differentstd::begin
objects at different addresses. In theory, that could cause problems. - I don’t know how to constrain a generic lambda.
- With automatic return type deduction, invalid calls to
begin
cause a hard error rather than being SFINAE’ed away. That might not be a huge problem forstd::begin
, but it most certainly is a huge problem forstd::__detail::begin
. Thebegin
overloads found by ADL must use SFINAE (or concept checks); otherwise, you would end up trying to call.begin()
on some object that doesn’t have a.begin()
member function.
In short, even in C++14, I think we need the ugly hackery I show. Maybe C++17 will bring relief.
I’m surprised you’ve gone through those __static_const hoops. I understand your suggestion to use functors has raised concerns regarding ODR, but I can’t say I’ve found them compelling. Since those functors are meant to be empty types, their value representation is made up of 0 bytes. That makes quite hard it for, say, an inline function in a header to do anything untoward.
Sure, it’s possible to notice different addresses if you go out of your way to do it. The thing is, use of empty functors is not terribly novel in itself (to pass to higher-order algorithms and so on), and we’ve never needed all references to e.g. boost::range_detail::reverse_forwarder to refer to the same object (which, I feel the need to reiterate, is empty and as good as any other object of the same type). Nor, putting functors aside, have we had that need for e.g. std::placeholders::_1. That’s not to mention all the upcoming variable templates giving way to e.g. std::is_lvalue_reference_v (as per STL’s n3932). None of those had concerns raised about ODR, so what gives? Why the backlash against internal linkage const *now*? Perhaps I’ve missed something, in which case I would be thankful for an explanation.
I don’t want my (somewhat fixated) tangent to be too disheartening though: if I have nothing to say on the actual matter at hand, it’s because I find your arguments ironclad. I feel that if you had mentioned the other solutions we’ve been using so far (e.g. boost::range::begin as an ADL-aware forwarder to range_begin), the advantages of yours would have stood out more, too.
Variable templates won’t suffer from the ODR problem since they’re templates. The compiler guarantees that all the symbols resolve to the same instantiation. With non-template global objects, though, the story is different. Every translation unit would get its own copy, and addresses would be different.
Is it a problem worth solving? I really don’t know. A few extra keystrokes solves the problem, so why not? That’s my reasoning.
I see. Come C++14 then a variable template version of __static_const (or just __const or constant then!) of the same trick would probably reach my own threshold of what makes for a few extra keystrokes.
I’m not familiar enough with the feature to say whether that works or not. Can you use variable templates to declare a non-template variable like
std::begin
?As somebody pointed out on reddit: People are allowed to add template specializations of these functions. So you couldn’t do this for std::swap because somebody has probably specialized that function, and replacing it with an object would break backwards compatibility.
That being said the solution is easy: Add another layer. So std::swap is a function which then calls a function object which then calls ADL swap. (see? easy)
I actually use a system somewhat similar to that in my format_it library: http://probablydance.com/2014/08/30/format_it-iterator-based-string-formatting/
I came up with that though because I needed both partial template specializations and ADL, so I had to have two layers.
Howard Hinnant also answered that question on stackoverflow here, where (in an update) he explains why specializing function templates in the
std
namespace is not the “right way” to hook these customization points. It’s true that code that specializesswap
in thestd
namespace would not survive this transition. I’m not suggesting we change the current version. I’m floating this as a possible solution for future versions of the standard should we ever decide to break backward compatibility, and also for the design of any new customization points.As for the add-another-layer-of-indirection solution, I would strongly discourage that. The reason why is because code that does the Two-Step must find the function object first. Otherwise, ADL is not disabled and you’ve reintroduced the name hijacking problem.
Why are you using an anonymous namespace for the function object begin? Is that needed?
The anonymous namespace is needed to prevent
std::begin
from being multiply defined in separate translation units. It’s a linker error.Ah, I get it now. Anon namespace to prevent multiple definitions of the reference to the object, BUT the address for the reference will be still the same thanks to the template instantiation __static_const::value. That is why ODR is not violated.
Complicated, but it works 😀
Since
std::begin
isconstexpr
andconstexpr
impliesconst
, and sinceconst
objects have internal linkage by default, are you sure the anonymous namespace is needed to avoid conflicting definitions? I am having similar problems with my Hana library, and I’m trying to understand.Just found out about http://ericniebler.github.io/std/wg21/D4381.html, which answers my question.
Pingback: Points de variation et ADL | C++ | Boost — Développement
Quick note: I think that for the concepts lite example you don´t need the trailing return type anymore, because the _RangeLike concept will refuse non-candidates, so there is no need for SFINAE.
Interesting post btw. But I can´t still figure out why you used an anonymous namespace for the function object.
Good point.
See my reply above. It’s to prevent a linker error.
This reification of functions, along with N4173/N4174, would enable slot-based interfaces as described in this article.
I like Herb Sutter’s proposal above uniform call syntax from a practical POV. Stroustrup has another floating around also that is more ambitious. With those I think it would be more reasonable to apply something similar to the article you pointed to:
Operator dot:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4173.pdf
Uniform call syntax:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4174.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4165.pdf
This would make free functions more reusable in general. Also would eliminate the need for a pipe operator for chaining operations, which I think it is another boilerplate removal. Left to right reading is imporant.
above = about 🙂 Sorry for the typo.
I know you say “judge me on my C++, not my wordpress” but going from reading against a black background to white background like that hurts my eyes.
As such, I had to give up reading due to visual fatigue. If you could change the colour scheme to something more eye-friendly I’d be happy to give this a thorough read.
Funny. Reading against a light background makes my eyes tired. I like the dark theme.
EDIT: I tried switching to a dark theme for the source code. I don’t love it, but maybe it’s better considering my blog’s overall theme is dark.
Keep it like this, please, my eyes rest with your blog. I even have a plugin installed in chrome to reverse the bright white color that many pages show.
Looks cool. I definitely want to check if I’m doing that swap/two step correctly. OTOH, seeing that decltype( FnType(…)){ return } offends my DRY nerves. IIRC you need the same jazz when defining a custom swap, it really needs fixing!
uh, that is: seeing decltype XXX followed by a return XXX bit…
Agreed. The DRY policy is part of what motivated the committee to add automatic return type deduction in C++14, but unfortunately the semantics aren’t the same, and in this case it makes a difference. I sometimes use a macro for this. 😛
decltype(auto)
ftw!Rein, I don’t think you’re understanding what I’m saying.
decltype(auto)
does not have the right semantics here since it doesn’t do SFINAE.Pingback: 在C++中实现Python的切片 - memleak.in | memleak.in
Consider say, a near-future case – std::size.
How do you think your technique compares to just having std::size(x) call std_size(x). ie have std_size be the customization point, and basically reserving all std_ names?
I think std_size is easier and maybe even more clear/explicit – makes it explicit that I am customizing a std customization point. (I do think it is a bit “ugly” however.)
I like making std::size a function object, but that is orthogonal to what free function it calls, I think.
Either way, I really want to solve this problem somehow, before we add yet more customization points.
Sure, I considered that. That risks some confusion. Folks will really want to define
size
for their types, both out of ignorance about how to hook the customization point, and because they will want to have a pithy way to get the size in their namespace. Then some people will callstd::size
directly and not get the overloaded user-definedsize
, or else they’ll do the Two Step and they will get the user-definedsize
. Worse, when they do the Two Step, they risk picking upsize
overloads they shouldn’t.How do you propose providing customisation points for types which do not have member functions, e.g. enums? If your customisation point is an object, it cannot be overloaded: http://goo.gl/SBK8pH
You seem to have misunderstood the technique. The function object is making an unqualified call to a free function. You can overload the function in the enum’s namespace.
I don’t think I misunderstood, though I skipped a bunch of reasoning.
If you have a function N::func and an enum N::E, then how do you customise N::func(N::E)?
Your technique works for things like std::begin operating on std::vector, because your unqualified call invokes your specialisation for ranges which ends up calling the std::vector::begin member function.
But the unqualified call is intended to allow ADL to find any customisation with the right signature. I could define a container like this:
namespace X {
class MyContainer {
…
// no begin() member here
…
};
MyContainer::MyIterator begin(MyContainer& c) { … }
}
… and a call to std::begin(instanceOfMyContainer) with your function object would work fine.
But while this works for X::MyContainer, I couldn’t define std::vector in this way, because writing:
namespace std {
template <typename… Args>
typename vector<Args…>::iterator begin(vector<Args…>& c) { … }
}
… would clash with the std::begin object, as that function can’t overload the object.
You could use the Barton-Nackman trick to allow ADL to work while still having your std::begin object… in theory anyway; GCC 4.9 didn’t like it when I tried.
Now, all this is irrelevant for std::begin and std::vector because they are simply not defined that way anyway.
But in the general case, if you wanted to have a central N::func as a function object which provides some default behaviour but is implemented as an unqualified func(T&) call to allow for customisation, then how would this work for types inside N which need non-default behaviour?
I mentioned enums because they don’t have member functions nor support Barton-Nackman and so are only customisable via an unqualified ADL lookup.
Imagine std::hash was to be specified as a function object. How would I customise it for std::memory_order?
(hope it’s now clearer what I’m getting at)
I understand your question now, and it’s a good one. There are two ways to deal with this. One would be to put the
enum
and its overload of the ADL customization point in a nested namespace, and then bring theenum
into the outer namespace with a using declaration.The second, cuter way is to achieve the same effect with anonymous namespaces. See the code below:
For me, this prints:
Hope this helps.
Would placing the enum/name in the nested unamed namespace, make this declaration declare different entity in every translation unit where the file is inluded? As far as I know unamed namespace is equivalent with namespace with unique identifier. So if have example enum declaration in header file and two source files A and B then the compiler will create different version (not compatible) of this enums. And this will cause a lot of problems if this type is used as function parameter.
Right. If this type needs external linkage, then an anonymous namespace is the wrong answer. You would need to use the named namespace with the using declaration.
“If the type of a and b overloads swap in its own namespace, a qualified call to std::swap won’t find it. It’s an all-too-easy mistake to make.”
And that’s why I always inserted my customizations into std namespace. I couldn’t even think of putting it into my namespace, why would I? If anything, just create a new namespace “customizations” and put it like this:
namespace std {
using namespace customizations_of_something;
}
That doesn’t work reliably, but you need to understand 2-phase look-up to know why. When parsing the body of a function template like
std::sort
, when the compiler sees an unqualified call toswap
it does normal lookup to find candidates. This is in phase 1, before the types of template parameters are known. Only those functions that have been seen so far (reading the file top to bottom) and that are in scope are added to the candidate set. In phase 2, once the types of the function arguments are known, the function is looked up again in the arguments’ associated namespaces (i.e., your namespaces).By putting your overloads in namespace
std
, you are hoping that phase 1 lookup will find them. But that is order dependent, since the compiler parses code in a linear fashion. If phase 1 happens not to see your overload before it sees the call toswap
, your overload will not be considered. (Unless by chance namespacestd
is an associated namespace of your type, in which case phase 2 will find it.)TL;DR: Dont’ do that.
I always put my std::swap (or actually std::to_string specialization) in the same header as my class, so when it needs to be called it should be always visible. Is there any way this scenario could fail? (BTW, gcc gives errors like “specialization of after instantiation”, so I assume that such errors don’t happen to me).
(side note: your commenting system ate the word from my comment, is that by design? I wonder if it shows up here…)
If you still think this is OK, you haven’t understood me. Let me try again. Let’s say you have a header Foo.h that defines class
Foo
and an overload ofswap
in namespacestd
. Now someone does:… after which they try to
std::sort
an array ofFoo
. The definition issort
is found in <algorithm>. The body ofsort
was parsed before the compiler saw yourswap
overload, so that overload isn’t considered by 1st phase lookup. And since there is noswap
inFoo
‘s associated namespace, 2nd phase lookup won’t find it either. Again, DON’T DO THIS.You will only get “specialization after instantiation” errors if you are actually specializing the primary template. Chances are, you’re overloading the function, not specializing the template. You’ll get no warning until your code stops compiling or silently does the wrong thing.
That makes it clear. Thanks!
Sorry but the link to your article ’emulating Concepts Lite in C++11′ is not correct. It points to an IP instead of to your domain.
http://104.154.63.30/2013/11/23/concept-checking-in-c11/ -> http://ericniebler.com/2013/11/23/concept-checking-in-c11/
Thanks for your blog!
Thanks, fixed!
Using sort on proxy iterators I got to the same conclusion,
iter_swap
should be a customization point and algorithms should use an unqualifiediter_swap
. Otherwisestd::sort
and many other algorithms will be suboptimal. It seems that GCC official position is different: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78231.Consider this:
namespace NS {
struct S {};
int * begin( S & s )
{
int* it = std::begin(); // Use the “default” version
return ++it;
}
}
int main() {
NS::S s;
int *p = std::begin(s); // calls NS::begin(s)
}
That particular example is a bit useless, but it’s a common technique to relying on the default version of an algorithm, and then, personalized it with the returned results. For example, an
iter_swap
overload which increments a counter or log something as side effect, to see how many times youriter_swap
method has been called.With your approach, overload for calling the “built-in” implementation and do something else would be impossible to do (unless you make public and with a different name the default version).