2

The following C++20 program:

#include <utility>
#include <cstddef>

template<typename... Args>
class C {
    template<size_t... I>
    static void call(
      std::index_sequence<I...> = std::index_sequence_for<Args...>{}
    ) {}
};

int main() {
    C<long int>::call();
}

fails to compile with error message:

test.cc: In static member function ‘static void C<Args>::call(std::index_sequence<I ...>) [with long unsigned int ...I = {}; Args = {long int}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int>]’:
test.cc:11:20: error: could not convert ‘std::index_sequence_for<long int>{}’ from ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’ to ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’
   11 |  C<long int>::call();
      |                    ^
      |                    |
      |                    integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>
test.cc:11:20: note:   when instantiating default argument for call to ‘static void C<Args>::call(std::index_sequence<I ...>) [with long unsigned int ...I = {}; Args = {long int}; std::index_sequence<I ...> = std::integer_sequence<long unsigned int>]’
test.cc: In function ‘int main()’:
test.cc:11:20: error: could not convert ‘std::index_sequence_for<long int>{}’ from ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’ to ‘integer_sequence<[...],#‘nontype_argument_pack’ not supported by dump_expr#<expression error>>’

Any ideas?

Update:

My current best workaround is to factor out default argument into two functions like:

template<typename... Args>
class C {
    static void call() {
      _call(std::index_sequence_for<Args...>{});
    }

    template<size_t... I>
    static void _call(std::index_sequence<I...>) {}
};

This seems to work around compiler bug (if that's what it is).

Update 2:

The below program fails for the same reason the original one does:

template<typename T> void f(T x = 42) {}

int main() { f(); }

so it's a feature not a bug.

Andrew Tomazos
  • 62,609
  • 36
  • 171
  • 294
  • Any useful info [here](https://groups.google.com/g/linux.debian.bugs.rc/c/ZzQj-Ie9Ql4)? seems to be a bug. [Here](https://stackoverflow.com/questions/57133186/g-and-clang-different-behaviour-when-stdmake-index-sequence-and-stdin) as well – Pat. ANDRIA Feb 25 '21 at 09:52
  • 2
    It's not a compiler bug. It's an annoyance of the language definition. The first version says there are an infinite number of `call` overloads (of a variety of template arities), which all have the same default parameter. C++ doesn't work backwards in type deduction to get you to a particular set of template parameters – Caleth Feb 25 '21 at 10:01
  • It does not seem to be bug since both clang and gcc do the same thing. I thing it's some obscure error with template deduction problem – bartop Feb 25 '21 at 10:01
  • @Caleth: It's not completely clear. Notice that `Args...` is a pack of the class template, not the member function template, therefore within a given instantiation of the class template `std::index_sequence_for{}` is a concrete object with a concrete type. – Andrew Tomazos Feb 25 '21 at 10:10
  • 1
    C++ does not fill in the template parameters `size_t... I` from the default argument – Caleth Feb 25 '21 at 10:11
  • @Caleth: Yeah I think you're right: `template void f(std::vector = std::vector{}) {}` fails but not until called with `f()`, so actually packs and `std::index_sequence` are a red herring here. – Andrew Tomazos Feb 25 '21 at 10:14
  • @Caleth: ...and actually even terser: `template void f(T x = 42) {}` fails when called with `f()` – Andrew Tomazos Feb 25 '21 at 10:19

2 Answers2

6

In general template argument deduction + default function arguments cause a lot of trouble. To simply fix it you can go with this:

#include <utility>
#include <cstddef>

template<typename... Args>
class C {
public:
    static void call() {
        call_impl(std::index_sequence_for<Args...>{});
    }

private:
    template<size_t... I>
    static void call_impl(std::index_sequence<I...> ) {
    }
};

int main() {
    C<long int>::call();
}
bartop
  • 9,609
  • 1
  • 21
  • 51
1

In C++20 you can also write a templated lambda to do exactly that without creating a new function:

//...
static void call() {
    return [&]<size_t... Is>(std::index_sequence<Is...>) {
        /* your code goes here... */ 
    }( std::index_sequence_for<Args...>{} );
}
Alex Vask
  • 69
  • 7