Yastanub
Yastanub

Reputation: 1237

Why do variadic functions not work with templates

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

Answers (2)

bolov
bolov

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

Richard Hodges
Richard Hodges

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

Related Questions