2

I'm new with templates, specially with parameter pack and I wonder if I can get the first value from the pack.

For example the following code:

template <typename T, typename... Args>
bool register(Args... args) {
    if (!Foo<T>(args..) {
        assert(std::is_same_v<std::string, args...[0]>);
        std::cerr << "Failed call Foo with " + args...[0] + "\n";
    }
}

How do I really get the first value in args...?

Worth to note that args... can contain different types (string, boolean, etc.)

Idan Cohen
  • 125
  • 1
  • 6

3 Answers3

5

Simpler in your case seems to change your function into:

template <typename T, typename Arg, typename... Args>
bool register(Arg arg, Args... args) {
    if (!Foo<T>(arg, args...) {
        assert(std::is_same_v<std::string, Arg>);
        std::cerr << "Failed call Foo with " + arg + "\n";
    }
}

and from the assert, even

template <typename T, typename... Args>
bool register(const std::string& s, Args... args) {
    if (!Foo<T>(s, args...) {
        std::cerr << "Failed call Foo with " + s + "\n";
    }
}

else <tuple> provides some useful tools:

template <typename T, typename... Args>
bool register(Args... args) {
    if (!Foo<T>(args...) {
        assert(std::is_same_v<std::string,
                              std::tuple_element_t<0, std::tuple<Args...>>);
        std::cerr << "Failed call Foo with "
            + std::get<0>(std::tie(args...)) + "\n";
    }
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
5

You can use lambda to extract the first parameter:

template<typename T, typename... Args>
bool register(Args... args) {
  if (!Foo<T>(args...)) {
    auto& first = [](auto& first, auto&...) -> auto& { return first; }(args...);
    static_assert(std::is_same_v<std::string,
                                 std::remove_reference_t<decltype(first)>>);
    std::cerr << "Failed call Foo with " + first + "\n";
  }
}
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • I meant *"non-POD class types are conditionally-supported"* ([cppreference](https://en.cppreference.com/w/cpp/language/variadic_arguments#Default_conversions)). it might produce [warning for msvc C4839](https://learn.microsoft.com/en-GB/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4839) or even compile error [Demo](https://godbolt.org/z/v589M584T). using template (`auto& first, auto&&...`) instead of C-ellipsis (`auto& first, ...`) to have full control. – Jarod42 Aug 03 '22 at 10:06
  • @Jarod42 Ouch, I've got your point, that is indeed a potential problem, thanks. – 康桓瑋 Aug 03 '22 at 10:17
1

I normally use the solution described above, just add an explicit extra template parameter for the first parameter. If you cannot do that this also works :

#include <type_traits>

namespace details
{
    template<typename type_t, typename... args_t>
    struct deduce_first
    {
        using type = type_t;
    };
}

template<typename... args_t>
using first_t = typename details::deduce_first<args_t...>::type;

template<typename... args_t>
bool register_f(args_t&&... args)
{
    static_assert(std::is_same_v<first_t<args_t...>, bool>, "first argument should have type bool");
    return true;
}

int main()
{
    register_f(true, 1.0);
    // register_f(1.0); <== does indeed not complie

    return 0;
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19