template boy
template boy

Reputation: 10480

How can I throw an exception when a template argument is invalid?

I have this code and I'm trying to get compile-time access to a tuple with std::get. When the user inputs a number that is out of bounds in the tuple I'd like to throw an exception. Unfortunately, I can't get that to work so I made the code return the first element in the array if it is out of bounds.

#include <tuple>

template <class... T>
struct input
{
    std::tuple<T...> var;
    input(T&&... t) : var(std::forward<T>(t)...) {}

    template <
          std::size_t N,
          bool in_range = 0 <= N && N < std::tuple_size<decltype(var)>::value
     >
    auto get()
          -> typename std::tuple_element<in_range ? N : 0, decltype(var)>::type&&
    {
        return std::move( std::get<in_range ? N : 0>(var) );
    }
};

template <class... Args>
void f(Args&&... args)
{
    auto arguments = input<Args...>(std::forward<Args>(args)...);

    arguments.template get<9>(); // returns 2 but I'd rather throw an exception
}

int main()
{
    f(2, 4, 6, 8);
}

How can I throw an exception or at the very least use a static_assert trick to get this to work?

Upvotes: 2

Views: 2086

Answers (2)

Andy Prowl
Andy Prowl

Reputation: 126452

Exceptions are meant for reporting run-time errors, while you are dealing with a compile-time programming issue here.

If you mind the not-very-transparent error the compiler would give you for trying to instantiate tuple_element<> with an out-of-bounds index, you could use SFINAE to get prevent your function from getting instantiated at all during overload resolution:

template <class... T>
struct input
{
    std::tuple<T...> var;
    input(T&&... t) : var(std::forward<T>(t)...) {}

    template<std::size_t N,
        typename std::enable_if<(N < std::tuple_size<decltype(var)>::value)>::
            type* = nullptr>
    auto get() -> typename std::tuple_element<N, decltype(var)>::type&&
    {
        return std::move( std::get<N>(var) );
    }
};

If you wish to add a clearer static_assert(), you could do so by adding an overload which gets picked only when N is out-of-bounds:

    template<std::size_t N,
        typename std::enable_if<(N >= std::tuple_size<decltype(var)>::value)>::
            type* = nullptr>
    void get()
    {
        static_assert(N < std::tuple_size<decltype(var)>::value, "OOB");
    }

Here is a live example.

Upvotes: 3

Kerrek SB
Kerrek SB

Reputation: 477140

Try this:

template <class... Args>
void f(Args&&... args)
{
    static_assert(sizeof...(Args) > 9, "Invalid tuple size");

    auto arguments = input<Args...>(std::forward<Args>(args)...);

    arguments.template get<9>();
}

(Throwing an exception makes no sense, since exceptions are for *exceptional runtime flow control", and not for static compilation error checking.)

Upvotes: 2

Related Questions