1

Why doesn't the following compile:

template <int...is> void g() {}

template <typename...Ts> void f(Ts...ts) {
    g<ts...>();
}

int main() {
    f(1,2);
}

it fails with

error: no matching function for call to 'g<ts#0, ts#1>()'
|     g<ts...>();
|     ~~~~~~~~^~

I understand that in general this is not possible since f can be called with anything.

I am curious if there is a way to make this work in cases such as here where the compiler clearly sees the compile time constant arguments in the call to f?

giik
  • 117
  • 6

2 Answers2

2

There is this way to do it:

f(std::integral_constant<int, 1>{}, std::integral_constant<int, 2>{});

// Or your own structs
template<int V>
struct i_t {
    constexpr operator int() const { return V; }
};

template<int V>
inline constexpr i_t<V> i;

f(i<1>, i<2>);

Since std::integral_constant<T, N>::operator T() just returns N so is a constant expression, and when you have g<ts...>, it calls operator int on each argument in the pack, which doesn't use the value of any ts.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • A good one, thanks @Artyer. Is "when you have g, it calls operator int on each argument in the pack" specified by the standard somewhere (like calling the ctor of T with the argument, the pack in this case)? – giik Aug 25 '21 at 17:46
  • @giik A non-type template argument should be a "converted constant expression" of the type of the template-parameter (here, `int`) (see: [temp.arg.nontype]p2), and a converted constant expression can be implicitly converted using user-defined conversions (see: [expr.const]p(10.1)). So there is an implicit conversion and it is used – Artyer Aug 25 '21 at 17:55
0

You can only use typename or class with parameter packs, so int s wont work. https://en.cppreference.com/w/cpp/language/parameter_pack

There are ways to only make f work for only ints though, I made this example for you. If you have any questions let me know.

#include <utility>
#include <type_traits>

// Create a constexpr (evaluated at compile time) to check
// if all arguments are ints.
template <typename... Ts>
constexpr bool are_all_ints()
{
    // conjunction is a 'logical and' when doing template metaprogramming
    // is_same checks the types , the syntax is what it is to expand for all template parameters
    return std::conjunction_v<std::is_same<int, Ts>...>;
}

// now you can either use a static assert to give a compile time warning
template <typename... Ts>
void g(Ts&&... values)
{
    static_assert(are_all_ints<Ts...>(), "this function only works with ints");
}

// or you can chose to completely hide the function
// f2 for anything that doesn't have all ints.
template <typename... Ts>
std::enable_if_t<are_all_ints<Ts...>(), void> g2(Ts&&... values)
{
}

template <typename... Ts>
void f(Ts&&... ts)
{
    g(std::forward<Ts>(ts)...);
}

template <typename... Ts>
void f2(Ts&&... ts)
{
    g2(std::forward<Ts>(ts)...);
}

int main()
{
    f(1, 2);
    //f(1.0, 2.0);
    //f2(1.0, 2.0);
}
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • 1
    *You can only use typename or class with parameter packs, so int s wont work.* this is totally false. The first syntax in your link even has *A non-type template parameter pack with an optional name* – NathanOliver Aug 25 '21 at 14:46
  • @NathanOliver Indeed defining template f() is allowed, and then make instances like f<1,2,3>(), And you can expand the ts... in the body like initializing a vector with it : std::vector v{ts...}; ). But expanding those ints as actual parameters to the function won't work. void f(ts... values), that will not compile – Pepijn Kramer Aug 25 '21 at 14:59