Blag
He's not dead, he's resting
Tag Archives: c++0x
C++ Explicit Template Instantiation Hate Redux
November 27, 2009
Posted by on Today’s hatred of C++ is brought to you by the section [temp.explicit]
:
A definition of a class template or class member template shall be in scope at the point of the explicit instantiation of the class template or class member template.
Unfortunately, it doesn’t “explicit instantiation definition” there, so you can’t do an explicit instantiation declaration when you only have a class declaration available. I can’t figure out what changing this would break, and whether it’s just an omission (explicit instantiation declarations are new in C++0x, but explicit instantiations are not) or a deliberate restriction.
Whilst we’re on the subject, not being able to use typedef names when explicitly instantiating is still a pain in the arse too, although the implications of allowing that are almost certainly moderately icky.
Assorted C++ Linkage
September 5, 2009
Posted by on - A talk by Bjarne Stroustrup on the design of C++0x.
- Range based for loops in C++0x without concepts, good to know this hasn’t been dropped.
- Making
std::list::size()
O(1) [PDF], a shame to see this hasn’t been dropped. - And on a less technical note, filtering compiler optimisation flags is not a solution from Diego. Unfortunately, he leaves out some of the worst offenders for flags that users think they can use that do change the meaning of programs:
-fvisibility-inlines-hidden
and-Wl,--as-needed
.
C++ Overload Resolution Hate
December 15, 2008
Posted by on Sometimes, C++’s overload resolution rules are a pain in the ass.
Let’s say we have the following:
#include <tr1/memory> struct Bar { Bar() { } }; struct Baz { Baz() { } }; struct Foo { explicit Foo(const std::tr1::shared_ptr<const Bar> &) { } explicit Foo(const std::tr1::shared_ptr<const Baz> &) { } };
Then the following is ambiguous, and won’t compile:
Foo foo(std::tr1::shared_ptr<Bar>(new Bar));
Here’s why: Neither constructor exactly matches the argument given, so the compiler falls back to construction and type conversions. std::tr1::shared_ptr<T_>
has an implicit constructor template <typename U_> shared_ptr(const shared_ptr<U_> &)
, which is good because it lets you use a shared pointer to a derived class when a shared pointer to a base class is expected. But that conversion can take place for all U_
, which means the compiler doesn’t know whether you want to convert to a shared pointer to const Bar
or const Baz
— it isn’t until the constructor body is instantiated that the compiler finds that only one of the two conversions will compile successfully.
So, one has to be explicit when creating the shared pointer:
Foo foo(std::tr1::shared_ptr<const Bar>(new Bar));
Except, usually we create shared pointers using a helper function, to avoid specifying the type name twice:
template <typename T_> std::tr1::shared_ptr<T_> make_shared_ptr(T_ * const t) { return std::tr1::shared_ptr<T_>(t); }
So we’re stuck having to use a slightly weird looking allocation:
Foo foo(make_shared_ptr(new const Bar));
Incidentally, C++0x has a std::make_shared
which is a lot better than this, but it requires rvalue references and std::forward
to work. It would look like this:
Foo foo(std::make_shared<Bar>());
Or, if we still need the const
:
Foo foo(std::make_shared<const Bar>());
Why might we not need the const
? The current C++0x draft standard includes the wording “[the template] constructor shall not participate in the overload resolution unless U_ *
is implicitly convertible to T_ *
“, which presumably means implementations have to solve the problem using concepts to restrict the template constructor.
And, of course, there’s one final gotcha. The new const Foo
form is only legal if Foo
has a user defined constructor. I have no idea why, but C++03 explicitly says so.
Making Paludis Compile with C++0x
August 12, 2008
Posted by on I managed to get gcc 4.4 svn to compile, so I decided to see just how badly the experimental C++0x support would break Paludis. Turns out, not too badly. Firstly, things caught by increased strictness or general rearrangement of headers:
- We had a few extra semicolons lying around. These now generate warnings, so we might as well shut them up. [fix]
- We weren’t including
<stdint.h>
to getuintptr_t
. Things were working by fluke because other headers were including it. [fix] - We were using
::rename
rather thanstd::rename
. [fix]
Then, the real issues:
- n2246 adds a
std::next
. Paludis has apaludis::next
. ADL means this sometimes causes confusion. To keep compatibility with non-0x compilers, we useusing
to getstd::next
intopaludis::
where necessary. [fix] std::list<>::push_back
is now overloaded on rvalue references, so we can no longer easily get a PMF. If we were only interested in 0x, we’d use a lambda, but for backwards compatibility we write a wrapper function instead. (Or we could use thestatic_cast
hack, but that’s horribly unreadable.) [fix]
All in all, not too bad. I suspect things will get a bit messier if a concept-enabled standard library makes it into the final proposal, but that can be dealt with later…
Implementing Active Objects using Smart Pointers
June 11, 2008
Posted by on An Active Object is, essentially, a threaded design pattern where an object is always called from the same thread, and where a proxy provides synchronised access to that object. It’s useful in cases where there’s little or no parallelism possible due to the underlying object’s state, and where implementing manual locking would be a nuisance.
The problem in C++, generally, is implementing the proxy. If the underlying object’s class has twenty methods, you have to implement twenty trivial wrapper methods for the proxy that obtain the lock and then forward. Whilst not difficult to do, it’s rather tedious.
But what if the proxy is a smart pointer? Then you could do proxy->method()
for any method the underlying class has, and you wouldn’t have to worry about writing wrapper methods. The question then is how to write Proxy<UnderlyingClass>::operator-> ()
.
It can’t simply return the underlying instance, since it needs to do locking. And it can’t obtain a lock and then return the underlying instance, since the lock would never be released.
But all is not lost. The standard has some rather interesting wording:
An expression
x->m
is interpreted as(x.operator->())->m
for a class objectx
of typeT
ifT::operator-> ()
exists and if the operator is selected as the best match function by the overload resolution mechanism.
Usually, implementations of operator-> ()
simply return SomeType *
. But with the way the standard is worded, they could instead return a second class instance, so long as that new class has operator-> ()
defined.
So we make Proxy<UnderlyingClass>::operator-> ()
return a temporary that, upon construction, obtains the shared lock, and upon destruction releases it. Then we rely upon the temporary’s operator-> ()
returning a pointer to the underlying object to get the actual method call.
Except… It’s not that simple. With current C++, the temporary has to be copyable (even though the compiler optimises out the copy), but typically mutex locks are noncopyable. With C++0x we’ll be able to return by rvalue reference, but until then we have to store the lock in a shared pointer rather than directly.
We’re going to start making use of this in Paludis to cut down on boilerplate code. A working implementation follows. We have a couple of refinements: the class is called ActiveObjectPtr
, and it’s parameterised by a pointer to the underlying class (we’ll see why in a later post — generally it’ll just be a std::tr1::shared_ptr
, but leaving it as a parameter lets us compose smart pointer types).
template <typename T_> class ActiveObjectPtr { private: T_ _ptr; std::tr1::shared_ptr<Mutex> _mutex; class Deref { private: const ActiveObjectPtr * _ptr; std::tr1::shared_ptr<Lock> _lock; public: Deref(const ActiveObjectPtr * p) : _ptr(p), _lock(make_shared_ptr(new Lock(*p->_mutex))) { } const T_ & operator-> () const { return _ptr->_ptr; } }; friend class Deref; public: ActiveObjectPtr(const T_ & t) : _ptr(t), _mutex(new Mutex) { } ActiveObjectPtr(const ActiveObjectPtr & other) : _ptr(other._ptr), _mutex(other._mutex) { } ~ActiveObjectPtr() { } ActiveObjectPtr & operator= (const ActiveObjectPtr & other) { if (this != &other) { _ptr = other._ptr; _mutex = other._mutex; } return *this; } Deref operator-> () const { return Deref(this); } };
At this stage, it’s not really a proper smart pointer. It doesn’t (and probably shouldn’t) have operator*
, and it ignores the const
issue. Handling these is left as an easy exercise for the reader.
Paludis Ruby Bindings and Template Classes
June 6, 2008
Posted by on A PackageID
in Paludis supports various actions. An Action
is represented by a subclass instance, such as InstallAction
or ConfigAction
, some of which carry member data (for example, an InstallAction
carries information about the target repository for the install).
To perform an action, the PackageID::perform_action
method is used. But not all IDs support all actions — you can’t, for example, uninstall a package that isn’t installed, and not all EAPIs support the ‘pretend’ action. So there’s a second method, PackageID::supports_action
, that returns a bool saying whether an action is supported.
That’s all very well, but it would require constructing an Action
subclass instance just for querying purposes. There’s not much point in this. So we make PackageID::supports_action
take a SupportsActionTest<SomeAction>
parameter rather than an Action
. Unlike the base Action
subclass, the SupportsActionTest<T_>
template class carries no member data, so it doesn’t need fancy construction. This lets us do this:
if (my_package_id->supports_action(SupportsActionTest<FetchAction>())) { FetchAction fetch_action(_imp->fetch_options); my_package_id->perform_action(fetch_action); }
This pattern crops up in two other places. To speed up certain queries, we can ask a Repository
whether some of its IDs might support a particular action. The Repository::some_ids_might_support_action
method will always return true if any of its IDs support a particular action, and might return false if it’s known for sure that none of them will (this weasel wording is necessary because we might, for example, have a repository full of ebuilds with unsupported EAPIs, and unsupported EAPIs means no actions are possible).
Similarly, the new filter system has filter::SupportsAction<ActionClass>
. The implementation of this filter uses the Repository
and PackageID
methods to be as lazy as possible.
Which is all well and good, in C++, but with bindings things get a bit icky since templates don’t translate naturally. In Ruby, we used to have a bunch of classes. SupportsInstallActionTest.new()
would be like SupportsActionTest<InstallAction>()
, SupportsConfigActionTest.new()
would be like SupportsActionTest<ConfigAction>
and so on. This isn’t particularly nice.
It occurred to me that SupportsActionTest.new(InstallAction)
is legal syntactically in Ruby. It passes the value InstallAction
, which is a variable of class Class
, as the parameter. Then we have to screw around a bit in the bindings code, remembering that SomeClass <= OtherClass
in Ruby means “SomeClass
is or is a subclass of OtherClass
“:
/* * Document-method: SupportsActionTest.new * * call-seq: * SupportsActionTest.new(ActionClass) -> SupportsActionTest * * Create new SupportsActionTest object. The ActionClass should be, e.g. InstallAction. */ static VALUE supports_action_test_new(VALUE self, VALUE action_class) { std::tr1::shared_ptr<const SupportsActionTestBase> * ptr(0); try { if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, install_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<InstallAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, installed_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<InstalledAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, uninstall_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<UninstallAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, pretend_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<PretendAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, config_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<ConfigAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, fetch_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<FetchAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, info_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<InfoAction>())); else if (Qtrue == rb_funcall2(action_class, rb_intern("<="), 1, pretend_fetch_action_value_ptr())) ptr = new std::tr1::shared_ptr<const SupportsActionTestBase>(make_shared_ptr(new SupportsActionTest<PretendFetchAction>())); else rb_raise(rb_eTypeError, "Can't convert %s into an Action subclass", rb_obj_classname(action_class)); VALUE tdata(Data_Wrap_Struct(self, 0, &Common<std::tr1::shared_ptr<const SupportsActionTestBase> >::free, ptr)); rb_obj_call_init(tdata, 0, &self); return tdata; } catch (const std::exception & e) { delete ptr; exception_to_ruby_exception(e); } } /* * Document-class: Paludis::SupportsActionTest * * Tests whether a Paludis::PackageID supports a particular action. */ c_supports_action_test = rb_define_class_under(paludis_module(), "SupportsActionTest", rb_cObject); rb_define_singleton_method(c_supports_action_test, "new", RUBY_FUNC_CAST(&supports_action_test_new), 1);
Not exactly pretty, for now. Hopefully when C++0x comes along we’ll be able to invent some obscene hack involving std::initializer_list
, lambdas and std::find_if
which will make the whole thing somewhat more elegant.
Unfortunately, the Python bindings are still stuck using package_id.supports_action(SupportsFetchActionTest())
etc. So far as I can see there’s no nice way to get the same effect whilst using boost.python, even though Python lets you pass classes around in a similar manner to Ruby.