Reputation: 1237
i wanted to write a function which compares several values and checks which of them is the smallest. I wanted to implement that as a variadic function template. I know there is a feature for that purpose since C++11 but for reasons i can't use that at the moment. So i tried using <cstdarg>
library but stumbled across some problems. For some reason the offsets of the arguments are calculated wrong when i use the function as a template. If i implement the function explicitely with a fixed type i have no problems.
My code:
#include <cstdarg>
#include <iostream>
#include <limits>
template <typename T>
T smallestOf(const int count, const T val1, ... ) { /* I use val1 to determine the template type */
va_list args;
va_start(args, val1);
T smallestVal = std::numeric_limits<T>::max();
for(int i = 0; i < count; i++) {
T nextVal = va_arg(args, T);
std::cout << "nextVal: " << nextVal << std::endl;
if(nextVal < smallestVal) smallestVal = nextVal;
}
va_end(args);
return smallestVal;
}
int main() {
std::cout << "Smallest value: " << smallestOf(3, 10, 20, 30) << std::endl;
}
which produces following output:
nextVal: 20
nextVal: 30
nextVal: 4217000
Smallest value: 20
which looks like the function reads past its memory as the offsets are wrong. Why is that?
Upvotes: 0
Views: 81
Reputation: 75707
T smallestOf(const int count, const T val1, ... )
when you call like this: smallestOf(3, 10, 20, 30)
the varargs are 20 30
(because 10
is val1
). So you need count - 1
.
Also strong recommendation: don't use varargs. Use variadic templates or std::initializer_list
You say you don't have access to C++11 so unfortunately don't have access to neither variadic templates or initializer lists.
Well, here is my gift for you:
template <class T> T min(T e1) { return e1; }
template <class T> T min(T e1, T e2) { return e1 < e2 ? e1: e2; }
template <class T> T min(T e1, T e2, T e3) { return min(e1, min(e2, e3)); }
template <class T> T min(T e1, T e2, T e3, T e4) { return min(e1, min(e2, e3, e4)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5) { return min(e1, min(e2, e3, e4, e5)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5, T e6) { return min(e1, min(e2, e3, e4, e5, e6)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5, T e6, T e7) { return min(e1, min(e2, e3, e4, e5, e6, e7)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8) { return min(e1, min(e2, e3, e4, e5, e6, e7, e8)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8, T e9) { return min(e1, min(e2, e3, e4, e5, e6, e7, e8, e9)); }
template <class T> T min(T e1, T e2, T e3, T e4, T e5, T e6, T e7, T e8, T e9, T e10) { return min(e1, min(e2, e3, e4, e5, e6, e7, e8, e9, e10)); }
You might be tempted to say that is suboptimal or that you could group the calls so that there are less calls, but any decent compiler will inline and optimize all these calls for you. Both clang
and gcc
compile min<int,....>
with 10 params with just branchless mov
cmp
and cmov
instructions.
Upvotes: 4
Reputation: 69882
Just noticed the comment that no c++11 is available, but since I've just written it, here's an example of a variadic solution written in c++17.
Maybe it'll be useful in future.
#include <iostream>
#include <utility>
#include <type_traits>
template<class T, class...Rest>
auto smallestOf(T const& val1, Rest const&...rest)
-> std::enable_if_t<std::is_same_v<std::common_type_t<T, Rest...>, T>, T const&>
{
auto* current = std::addressof(val1);
if constexpr (sizeof...(Rest) > 0)
{
auto check = [](T const* x, T const* y)
{
return std::addressof(std::min(*x, *y));
};
((current = check(current, std::addressof(rest))),...);
}
return *current;
}
int main() {
std::cout << "Smallest value: " << smallestOf(10) << std::endl;
std::cout << "Smallest value: " << smallestOf(20, 10) << std::endl;
std::cout << "Smallest value: " << smallestOf(30, 10, 20) << std::endl;
std::cout << "Smallest value: " << smallestOf(30, 10, 40, 20) << std::endl;
}
http://coliru.stacked-crooked.com/a/600a91f1678763b2
Upvotes: 0