Reputation: 13436
This might be an easy question, I don't master C++11 templates at all.
I have a generic vector class that is not std::vector<T>
for performance reasons (very specific code).
I have observed that checking whether T
is a POD or not and, when it is, perform special computations, is much more efficient than not :
void vec<T>::clear() {
if (!std::is_pod<T>::value) {
for (int i = 0; i < size; i++) {
data[i].~T();
}
}
size = 0;
}
Here, I don't call the destructor of T
for each item (size
can be really huge) and performance is really boosted. But the test if (!std::is_pod<T>::value)
is useless once the template was compiled : rather than being compiled to :
void vec<int>::clear() {
if (false) {
for (int i = 0; i < size; i++) {
data[i].~int();
}
}
size = 0;
}
I want it to be compiled to :
void vec<int>::clear() {
size = 0;
}
Is the compiler "clever" enough to skip if (false)
blocks or if (true)
tests ? Do I have to write that code somewhat differently ?
Upvotes: 12
Views: 857
Reputation: 545913
Is the compiler "clever" enough to skip if (false) blocks or if (true) tests?
Yes, definitely. Dead code elimination is a trivial optimisation that is performed routinely. Its existence is also crucial to make many debugging libraries work efficiently (= without runtime overhead in release mode).
But I would probably still rewrite this to make it visible to the reader that this is a compile-time distinction, by overloading the function based on is_pod
:
void vec<T>::do_clear(std::true_type) { }
void vec<T>::do_clear(std::false_type) {
for (int i = 0; i < size; i++) {
data[i].~T();
}
}
void vec<T>::clear() {
do_clear(std::is_trivially_destructible<T>());
size = 0;
}
In the above code I’m using is_trivially_destructible
instead of is_pod
to make the code more self-explanatory, as suggested by Nicol in the comments. This technique is commonly employed in standard library implementations and other libraries. It’s known as tag dispatching.
Upvotes: 26
Reputation: 275760
Dead code elimination is a common optimization.
However, if you do not trust your compiler to do any optimization at all, you could create a static if template
library.
Skip down to the punchline if you don't feel like reading a bunch of pretty horrible hacks.
#include <utility>
#include <type_traits>
template<bool b>
struct static_if_t {
static_if_t( static_if_t const& ) = default;
static_if_t() = default;
static_if_t( static_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};
template<bool dead>
struct static_if_branch {};
template<bool b>
struct static_else_if_t {
static_else_if_t( static_else_if_t const& ) = default;
static_else_if_t() = default;
static_else_if_t( static_else_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};
template<bool b>
static_if_t<b> static_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
template<bool b>
static_else_if_t<b> static_else_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
static auto static_else = static_else_if<true>;
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<true> operator*( static_if_t<true>, Lambda&& closure )
{
std::forward<Lambda>(closure)();
return {};
}
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<false> operator*( static_if_t<false>, Lambda&& /*closure*/ )
{
return {};
}
template<typename Unused>
static_if_branch<true> operator*( static_if_branch<true>, Unused&& ) {
return {};
}
static_if_t< true > operator*( static_if_branch<false>, static_else_if_t<true> ) {
return {};
}
static_if_t< false > operator*( static_if_branch<false>, static_else_if_t<false> ) {
return {};
}
And here is the punchline:
#include <iostream>
int main() {
static_if<true>* [&]{
std::cout << "hello\n";
} *static_else* [&]{
std::cout << "doom\n";
};
static_if<false>* [&]{
std::cout << "doom the\n";
} *static_else* [&]{
std::cout << "world\n";
};
static_if<false>* [&]{
std::cout << "fello\n";
} *static_else_if<false>* [&]{
std::cout << "yellow\n";
} *static_else_if<false>* [&]{
std::cout << "hehe\n";
};
static_if( std::is_same<int, int>() )* [&]{
std::cout << "int is int\n";
};
static_if( std::is_same<double, double>() )* [&]{
std::cout << "double is double\n";
} *static_else_if( std::is_same<int, double>() )* [&]{
std::cout << "int is double\n";
} *static_else* [&]{
std::cout << "sky is not blue\n";
};
}
but why would you want to do that? Live example
(note that there are two syntaxes the above static_if
-- one static_if<compile time boolean expression>
, and another static_if( std::is_whatever<blah>() )
).
Now, while the above is completely insane, the above technique would let you write a compile time trinary operator that allows a different type based on which branch is picked. Which is neat.
Ie, something like this:
auto result = trinary<std::is_same<A,B>::value>% 7 | 3.14;
and the type of result
would be int
if A
and B
are the same type, and double
if they differ. Or even:
auto result = meta_trinary<std::is_same<A,B>::value>% [&]{return 7;} | [&]{return 3.14;};
if you prefer, allowing entire blocks of code to be conditionally evaluated, and the conditional type of the return value to be stored.
Upvotes: 2
Reputation: 68698
There is a language feature called pseudo destructors which is specifically designed for what you want to do. Basically given a type template parameter T you can syntactically call a destructor for it, and if, when instantiated, T is a scalar type (because for example it is a fundamental type like an int
) it will compile and generate a no-op in its place.
For the remainder of POD types that are not scalar, they have trivial destructors, so will likewise generate a no-op.
Any production compiler on even the lowest optimization setting will elide a loop over a no-op. So you can safely write:
void vec<T>::clear() {
for (int i = 0; i < size; i++) {
data[i].~T();
}
size = 0;
}
Basically, you are trying to solve an imaginary performance problem the compiler is already taking care of for you.
Upvotes: 10