tuple index out of bounds, variadic template

Viewed 34

DEMO

How I could read the args of this variadic template, as it can be any number of arguments?

#include <tuple>
#include <string>
#include <iostream>

enum class traceErr
{
   err1, err2
};

template <typename... Args>
void TraceErr(traceErr err, Args&&... args)
{
    auto pack = std::make_tuple(std::forward<Args>(args)...);

    std::string str;

    switch (err)
    {
    case traceErr::err1:
        str = "Err: " + std::to_string(std::get<0>(pack));
    break;

    case traceErr::err2:
        str = "Err: " + std::to_string(std::get<0>(pack)) + " "
        + std::get<1>(pack);
    break;
    }

    std::cout << str << std::endl;
}

int main()
{
    TraceErr(traceErr::err1, 1);
    TraceErr(traceErr::err2, 2, "etc");  
}

In the example above I'm getting these errors:

static_assert failed: 'tuple index out of bounds

'get': no matching overload function found

Because the number of args being passed are different, how i could 'fix' it to work with any number of args?

1 Answers

For every specialization of TraceErr the whole function body is compiled and must be valid. If you're providing only 2 parameters and therefore the tuple size is 1, the expression

std::get<1>(pack)

is invalid.

The only way to ignore this kind of issue and keep the statement for both specializations would be to use if constexpr, but this requires the first function parameter to be changed to a template parameter.

enum class traceErr
{
   err1, err2
};

template <traceErr err, typename... Args>
void TraceErr(Args&&... args)
{
    auto pack = std::make_tuple(std::forward<Args>(args)...);

    std::string str;

    if constexpr (err == traceErr::err1)
    {
        str = "Err: " + std::to_string(std::get<0>(pack));
    }
    else
    {
        str = "Err: " + std::to_string(std::get<0>(pack)) + " "
             + std::get<1>(pack);
    }

    std::cout << str << std::endl;
}

int main()
{
    TraceErr<traceErr::err1>(1);
    TraceErr<traceErr::err2>(2, "etc");  
}

But why use std::tuple here at all? Just use a fold expression:

template <typename T, typename... Args>
void TraceErr(T&& arg, Args&&... args)
{
    std::cout << "Err: " << std::forward<T>(arg);
    ((std::cout << ' ' << std::forward<Args>(args)), ...);
    std::cout << '\n';
}

int main()
{
    TraceErr(1);
    TraceErr(2, "etc");
}

(Note: You could generate a fold expression for creating a string, but it may be preferrable to avoid the dynamic memory allocation required and directly write the results to the output stream.)

Related