Background: The usage of STL makes builds slow due to template code bloat: quite often the same methods are independently instantiated in many different translation units, being compiled and optimized many times. One way to avoid this object code duplication for template classes is to use explicit template instantiation and extern template declaration, but STL implementations do not support them. I'm trying to implement an equivalent of std::vector<T> which would support explicit instantiations.
Question: I have a template class vector<T>, and I want some of its methods to be removed if the template argument T does not satisfy some condition.
To make things worse, here are additional requirements:
vector<T>, regardless if whether T satisfies the condition or not.Discussion: For example, the method vector<T>::push_back(const T&) cannot work when T is not copy-constructible.
If I leave this method implementation intact, then compiler will produce error if I explicitly instantiate e.g. vector<unique_ptr<int>>, because it won't be able to copy unique_ptr<int>. This contradicts requirement 1, and makes the desired code bloat optimization impossible.
I can make two different implementations of vector<T>::push_back(const T&), one for copy-constructible types and one for the others. This can be done using SFINAE overloading or with a helper template class. The implementation for non-copy-constructible case can simply throw exception or call terminate. But then calling the method push_back for vector<unique_ptr<int>> will only crash during runtime. It won't generate compile error, as said in requirement 2.
Finally, there are several conditions, each of them rules out some methods. The type T can lack the following properties in various combinations: copy-constructible, copy-assignable, default-constructible, move-constructible. The solution from the related question seems to work for one condition only, which does not satisfy requirement 3.
The only idea I have left is to use some sort of partial specialization of the whole vector<T> combined with preprocessor hacks. But this would need something like 16 separate specializations, which sounds terrible.
P.S. It becomes pretty obvious to me that STL design is inherently dependent on the mechanics of implicit instantiation, which makes it very hard to reduce the code bloat caused by them.
You could let overload resolution and std::conditional work its magic. This doesn't suffer from the exponential amount of specializations as each method is predicated only on its own requirements.
template<typename T>
class vector_errors
{
public:
template<typename...>
void push_back(const T&)
{
static_assert(std::is_copy_constructible_v<T>, "T must be copy constructible");
}
};
class dummy
{
dummy(const dummy&) {} // disable construction
};
template<typename T>
class vector : vector_errors<T>
{
using base = vector_errors<T>;
public:
using base::push_back;
void push_back(const std::conditional_t<std::is_copy_constructible_v<T>, T, dummy>& t)
{
if constexpr(std::is_copy_constructible_v<T>)
{
// do stuff
}
}
};
template class vector<int>; // instantiates push_back
template class vector<std::unique_ptr<int>>; // error on push_back
Live
You can use SFINAE. It works exactly as per your requirements:
struct X
{
X() = default;
X(const X&) = delete;
};
template <class T> struct V
{
T* ptr_;
template <class U = T, decltype(U{std::declval<U>()})* = nullptr>
auto push_back(const T& val)
{
static_assert(std::is_same<U, T>::value, "explicit template parameter U provided.");
// ... code
}
// you can have this or not, depending on your preferences
// auto push_back(...) = delete;
};
// explicit instantiation OK
template struct V<int>;
// you then need to explicit instantiate all the method templates
template auto V<int>::push_back(const int&) -> void;
template struct V<X>; // explicit instantiation OK
// don't instantiate "disabled" methods
// template auto V<X>::push_back(const X&) -> void;
auto test()
{
V<X> v; // OK
v.push_back(X{}); // compile time error
}
You can explicitly instantiate the vector, you can create objects and you get a compile error when calling push_back:
<source>:34:7: error: no matching member function for call to 'push_back' v.push_back(X{}); ~~^~~~~~~~~ <source>:19:10: note: candidate template ignored: substitution failure [with U = X]: excess elements in struct initializer auto push_back(const U& val) ^
Alternately, if you leave the deleted push_back you get:
<source>:34:7: error: call to deleted member function 'push_back' v.push_back(X{}); ~~^~~~~~~~~ <source>:26:10: note: candidate function has been explicitly deleted auto push_back(...) = delete; ^ <source>:19:10: note: candidate template ignored: substitution failure [with U = X]: excess elements in struct initializer auto push_back(const U& val) ^
The only hassle is that you need to explicit instantiate all the method templates, but only those who are not disabled.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With